@rimori/client 2.2.0 → 2.3.0-next.2
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/.github/workflows/pre-release.yml +126 -0
- package/README.md +9 -13
- package/dist/cli/scripts/init/main.js +0 -0
- package/dist/cli/scripts/release/release.js +0 -0
- package/dist/controller/ExerciseController.d.ts +3 -1
- package/dist/controller/ExerciseController.js +4 -1
- package/dist/controller/SettingsController.d.ts +6 -2
- package/dist/controller/TranslationController.d.ts +4 -2
- package/dist/controller/TranslationController.js +40 -18
- package/dist/fromRimori/EventBus.js +49 -29
- package/dist/fromRimori/PluginTypes.d.ts +6 -6
- package/dist/index.d.ts +2 -1
- package/dist/plugin/CommunicationHandler.d.ts +11 -0
- package/dist/plugin/CommunicationHandler.js +9 -7
- package/dist/plugin/Logger.d.ts +1 -0
- package/dist/plugin/Logger.js +15 -3
- package/dist/plugin/RimoriClient.d.ts +12 -0
- package/dist/plugin/RimoriClient.js +32 -9
- package/example/worker/vite.config.ts +3 -0
- package/package.json +7 -1
- package/src/controller/ExerciseController.ts +6 -1
- package/src/controller/SettingsController.ts +7 -2
- package/src/controller/TranslationController.ts +51 -22
- package/src/fromRimori/EventBus.ts +105 -53
- package/src/fromRimori/PluginTypes.ts +28 -19
- package/src/index.ts +2 -1
- package/src/plugin/CommunicationHandler.ts +20 -7
- package/src/plugin/Logger.ts +15 -3
- package/src/plugin/RimoriClient.ts +32 -9
|
@@ -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> {
|
|
@@ -39,8 +39,8 @@ export class EventBusHandler {
|
|
|
39
39
|
private listeners: Map<string, Set<Listeners<EventPayload>>> = new Map();
|
|
40
40
|
private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
|
|
41
41
|
private static instance: EventBusHandler | null = null;
|
|
42
|
-
private debugEnabled
|
|
43
|
-
private evName
|
|
42
|
+
private debugEnabled = false;
|
|
43
|
+
private evName = '';
|
|
44
44
|
|
|
45
45
|
private constructor() {
|
|
46
46
|
//private constructor
|
|
@@ -50,12 +50,16 @@ 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
|
+
`[${
|
|
57
|
+
EventBusHandler.instance!.evName
|
|
58
|
+
}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`,
|
|
59
|
+
);
|
|
56
60
|
});
|
|
57
61
|
}
|
|
58
|
-
if (name && EventBusHandler.instance.evName ===
|
|
62
|
+
if (name && EventBusHandler.instance.evName === '') {
|
|
59
63
|
EventBusHandler.instance.evName = name;
|
|
60
64
|
}
|
|
61
65
|
return EventBusHandler.instance;
|
|
@@ -80,9 +84,9 @@ export class EventBusHandler {
|
|
|
80
84
|
* @param topic - The topic of the event.
|
|
81
85
|
* @param data - The data of the event.
|
|
82
86
|
* @param eventId - The event id of the event.
|
|
83
|
-
*
|
|
87
|
+
*
|
|
84
88
|
* The topic format is: **pluginId.area.action**
|
|
85
|
-
*
|
|
89
|
+
*
|
|
86
90
|
* Example topics:
|
|
87
91
|
* - pl1234.card.requestHard
|
|
88
92
|
* - pl1234.card.requestNew
|
|
@@ -96,7 +100,13 @@ export class EventBusHandler {
|
|
|
96
100
|
this.emitInternal(sender, topic, data || {}, eventId);
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
private emitInternal(
|
|
103
|
+
private emitInternal(
|
|
104
|
+
sender: string,
|
|
105
|
+
topic: string,
|
|
106
|
+
data: EventPayload,
|
|
107
|
+
eventId?: number,
|
|
108
|
+
skipResponseTrigger = false,
|
|
109
|
+
): void {
|
|
100
110
|
if (!this.validateTopic(topic)) {
|
|
101
111
|
this.logAndThrowError(false, `Invalid topic: ` + topic);
|
|
102
112
|
return;
|
|
@@ -105,7 +115,7 @@ export class EventBusHandler {
|
|
|
105
115
|
const event = this.createEvent(sender, topic, data, eventId);
|
|
106
116
|
|
|
107
117
|
const handlers = this.getMatchingHandlers(event.topic);
|
|
108
|
-
handlers.forEach(handler => {
|
|
118
|
+
handlers.forEach((handler) => {
|
|
109
119
|
if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
|
|
110
120
|
// console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
|
|
111
121
|
return;
|
|
@@ -132,8 +142,12 @@ export class EventBusHandler {
|
|
|
132
142
|
* @param ignoreSender - The senders to ignore.
|
|
133
143
|
* @returns An EventListener object containing an off() method to unsubscribe the listeners.
|
|
134
144
|
*/
|
|
135
|
-
public on<T = EventPayload>(
|
|
136
|
-
|
|
145
|
+
public on<T = EventPayload>(
|
|
146
|
+
topics: string | string[],
|
|
147
|
+
handler: EventHandler<T>,
|
|
148
|
+
ignoreSender: string[] = [],
|
|
149
|
+
): EventListener {
|
|
150
|
+
const ids = this.toArray(topics).map((topic) => {
|
|
137
151
|
this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
|
|
138
152
|
if (!this.validateTopic(topic)) {
|
|
139
153
|
this.logAndThrowError(true, `Invalid topic: ` + topic);
|
|
@@ -144,17 +158,35 @@ export class EventBusHandler {
|
|
|
144
158
|
}
|
|
145
159
|
const id = Math.floor(Math.random() * 10000000000);
|
|
146
160
|
|
|
147
|
-
//
|
|
148
|
-
const
|
|
161
|
+
// To prevent infinite loops and processing the same eventId multiple times
|
|
162
|
+
const blackListedEventIds: { eventId: number; sender: string }[] = [];
|
|
163
|
+
const eventHandler = (data: EventBusMessage) => {
|
|
164
|
+
if (blackListedEventIds.some((item) => item.eventId === data.eventId && item.sender === data.sender)) {
|
|
165
|
+
console.log('BLACKLISTED EVENT ID', data.eventId, data);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
blackListedEventIds.push({
|
|
169
|
+
eventId: data.eventId,
|
|
170
|
+
sender: data.sender,
|
|
171
|
+
});
|
|
172
|
+
if (blackListedEventIds.length > 100) {
|
|
173
|
+
blackListedEventIds.shift();
|
|
174
|
+
}
|
|
175
|
+
return (handler as unknown as EventHandler<EventPayload>)(data);
|
|
176
|
+
};
|
|
177
|
+
|
|
149
178
|
this.listeners.get(topic)!.add({ id, handler: eventHandler, ignoreSender });
|
|
150
179
|
|
|
151
|
-
this.logIfDebug(`Subscribed to ` + topic, {
|
|
180
|
+
this.logIfDebug(`Subscribed to ` + topic, {
|
|
181
|
+
listenerId: id,
|
|
182
|
+
ignoreSender,
|
|
183
|
+
});
|
|
152
184
|
|
|
153
185
|
return btoa(JSON.stringify({ topic, id }));
|
|
154
186
|
});
|
|
155
187
|
|
|
156
188
|
return {
|
|
157
|
-
off: () => this.off(ids)
|
|
189
|
+
off: () => this.off(ids),
|
|
158
190
|
};
|
|
159
191
|
}
|
|
160
192
|
|
|
@@ -165,33 +197,41 @@ export class EventBusHandler {
|
|
|
165
197
|
* @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
198
|
* @returns An EventListener object containing an off() method to unsubscribe the listeners.
|
|
167
199
|
*/
|
|
168
|
-
public respond(
|
|
200
|
+
public respond(
|
|
201
|
+
sender: string,
|
|
202
|
+
topic: string | string[],
|
|
203
|
+
handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>),
|
|
204
|
+
): EventListener {
|
|
169
205
|
const topics = Array.isArray(topic) ? topic : [topic];
|
|
170
|
-
const listeners = topics.map(topic => {
|
|
206
|
+
const listeners = topics.map((topic) => {
|
|
171
207
|
const blackListedEventIds: number[] = [];
|
|
172
208
|
//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
|
-
|
|
209
|
+
const finalIgnoreSender = !topic.startsWith('self.') ? [sender] : [];
|
|
210
|
+
|
|
211
|
+
const listener = this.on(
|
|
212
|
+
topic,
|
|
213
|
+
async (data: EventBusMessage) => {
|
|
214
|
+
if (blackListedEventIds.includes(data.eventId)) {
|
|
215
|
+
// console.log("BLACKLISTED EVENT ID", data.eventId);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
blackListedEventIds.push(data.eventId);
|
|
219
|
+
if (blackListedEventIds.length > 100) {
|
|
220
|
+
blackListedEventIds.shift();
|
|
221
|
+
}
|
|
222
|
+
const response = typeof handler === 'function' ? await handler(data) : handler;
|
|
223
|
+
this.emit(sender, topic, response, data.eventId);
|
|
224
|
+
},
|
|
225
|
+
finalIgnoreSender,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
this.logIfDebug(`Added respond listener ` + sender + ' to topic ' + topic, { listener, sender });
|
|
189
229
|
return {
|
|
190
|
-
off: () => listener.off()
|
|
230
|
+
off: () => listener.off(),
|
|
191
231
|
};
|
|
192
232
|
});
|
|
193
233
|
return {
|
|
194
|
-
off: () => listeners.forEach(listener => listener.off())
|
|
234
|
+
off: () => listeners.forEach((listener) => listener.off()),
|
|
195
235
|
};
|
|
196
236
|
}
|
|
197
237
|
|
|
@@ -206,7 +246,7 @@ export class EventBusHandler {
|
|
|
206
246
|
return;
|
|
207
247
|
}
|
|
208
248
|
|
|
209
|
-
let listener: EventListener | undefined;
|
|
249
|
+
let listener: EventListener | undefined = undefined;
|
|
210
250
|
const wrapper = (event: EventBusMessage<T>) => {
|
|
211
251
|
handler(event);
|
|
212
252
|
listener?.off();
|
|
@@ -221,15 +261,18 @@ export class EventBusHandler {
|
|
|
221
261
|
* @param listenerIds - The ids of the listeners to unsubscribe from.
|
|
222
262
|
*/
|
|
223
263
|
private off(listenerIds: string | string[]): void {
|
|
224
|
-
this.toArray(listenerIds).forEach(fullId => {
|
|
264
|
+
this.toArray(listenerIds).forEach((fullId) => {
|
|
225
265
|
const { topic, id } = JSON.parse(atob(fullId));
|
|
226
266
|
|
|
227
267
|
const listeners = this.listeners.get(topic) || new Set();
|
|
228
268
|
|
|
229
|
-
listeners.forEach(listener => {
|
|
269
|
+
listeners.forEach((listener) => {
|
|
230
270
|
if (listener.id === Number(id)) {
|
|
231
271
|
listeners.delete(listener);
|
|
232
|
-
this.logIfDebug(`Removed listener ` + fullId, {
|
|
272
|
+
this.logIfDebug(`Removed listener ` + fullId, {
|
|
273
|
+
topic,
|
|
274
|
+
listenerId: id,
|
|
275
|
+
});
|
|
233
276
|
}
|
|
234
277
|
});
|
|
235
278
|
});
|
|
@@ -246,7 +289,11 @@ export class EventBusHandler {
|
|
|
246
289
|
* @param data - The data of the event.
|
|
247
290
|
* @returns A promise that resolves to the event.
|
|
248
291
|
*/
|
|
249
|
-
public async request<T = EventPayload>(
|
|
292
|
+
public async request<T = EventPayload>(
|
|
293
|
+
sender: string,
|
|
294
|
+
topic: string,
|
|
295
|
+
data?: EventPayload,
|
|
296
|
+
): Promise<EventBusMessage<T>> {
|
|
250
297
|
if (!this.validateTopic(topic)) {
|
|
251
298
|
this.logAndThrowError(true, `Invalid topic: ` + topic);
|
|
252
299
|
}
|
|
@@ -255,8 +302,10 @@ export class EventBusHandler {
|
|
|
255
302
|
|
|
256
303
|
this.logIfDebug(`Requesting data from ` + topic, { event });
|
|
257
304
|
|
|
258
|
-
return new Promise<EventBusMessage<T>>(resolve => {
|
|
259
|
-
this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
|
|
305
|
+
return new Promise<EventBusMessage<T>>((resolve) => {
|
|
306
|
+
this.responseResolvers.set(event.eventId, (value: EventBusMessage<unknown>) =>
|
|
307
|
+
resolve(value as EventBusMessage<T>),
|
|
308
|
+
);
|
|
260
309
|
this.emitInternal(sender, topic, data || {}, event.eventId, true);
|
|
261
310
|
});
|
|
262
311
|
}
|
|
@@ -271,7 +320,7 @@ export class EventBusHandler {
|
|
|
271
320
|
|
|
272
321
|
// Find wildcard matches
|
|
273
322
|
const wildcard = [...this.listeners.entries()]
|
|
274
|
-
.filter(([key]) => key.endsWith(
|
|
323
|
+
.filter(([key]) => key.endsWith('*') && topic.startsWith(key.slice(0, -1)))
|
|
275
324
|
.flatMap(([_, handlers]) => [...handlers]);
|
|
276
325
|
return new Set([...exact, ...wildcard]);
|
|
277
326
|
}
|
|
@@ -283,32 +332,35 @@ export class EventBusHandler {
|
|
|
283
332
|
*/
|
|
284
333
|
private validateTopic(topic: string): boolean {
|
|
285
334
|
// Split event type into parts
|
|
286
|
-
const parts = topic.split(
|
|
335
|
+
const parts = topic.split('.');
|
|
287
336
|
const [plugin, area, action] = parts;
|
|
288
337
|
|
|
289
338
|
if (parts.length !== 3) {
|
|
290
|
-
if (parts.length === 1 && plugin ===
|
|
339
|
+
if (parts.length === 1 && plugin === '*') {
|
|
291
340
|
return true;
|
|
292
341
|
}
|
|
293
|
-
if (parts.length === 2 && plugin !==
|
|
342
|
+
if (parts.length === 2 && plugin !== '*' && area === '*') {
|
|
294
343
|
return true;
|
|
295
344
|
}
|
|
296
345
|
this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
|
|
297
346
|
return false;
|
|
298
347
|
}
|
|
299
348
|
|
|
300
|
-
if (action ===
|
|
349
|
+
if (action === '*') {
|
|
301
350
|
return true;
|
|
302
351
|
}
|
|
303
352
|
|
|
304
353
|
// Validate action part
|
|
305
|
-
const validActions = [
|
|
354
|
+
const validActions = ['request', 'create', 'update', 'delete', 'trigger'];
|
|
306
355
|
|
|
307
|
-
if (validActions.some(a => action.startsWith(a))) {
|
|
356
|
+
if (validActions.some((a) => action.startsWith(a))) {
|
|
308
357
|
return true;
|
|
309
358
|
}
|
|
310
359
|
|
|
311
|
-
this.logAndThrowError(
|
|
360
|
+
this.logAndThrowError(
|
|
361
|
+
false,
|
|
362
|
+
`Invalid event topic name. The action: ` + action + '. Must be or start with one of: ' + validActions.join(', '),
|
|
363
|
+
);
|
|
312
364
|
return false;
|
|
313
365
|
}
|
|
314
366
|
|
|
@@ -327,4 +379,4 @@ export class EventBusHandler {
|
|
|
327
379
|
}
|
|
328
380
|
}
|
|
329
381
|
|
|
330
|
-
export const EventBus = EventBusHandler.getInstance();
|
|
382
|
+
export const EventBus = EventBusHandler.getInstance();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// whole configuration of a plugin (from the database)
|
|
2
|
-
export type Plugin<T extends
|
|
2
|
+
export type Plugin<T extends object = object> = Omit<RimoriPluginConfig<T>, 'context_menu_actions'> & {
|
|
3
3
|
version: string;
|
|
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,14 +74,14 @@ 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
|
/**
|
|
72
81
|
* Rimori plugin structure representing the complete configuration
|
|
73
82
|
* of a Rimori plugin with all metadata and configuration options.
|
|
74
83
|
*/
|
|
75
|
-
export interface RimoriPluginConfig<T extends
|
|
84
|
+
export interface RimoriPluginConfig<T extends object = object> {
|
|
76
85
|
id: string;
|
|
77
86
|
/**
|
|
78
87
|
* Basic information about the plugin including branding and core details.
|
|
@@ -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/index.ts
CHANGED
|
@@ -12,7 +12,8 @@ export { Translator } from './controller/TranslationController';
|
|
|
12
12
|
export type { TOptions } from 'i18next';
|
|
13
13
|
export type { SharedContent, SharedContentObjectRequest } from './controller/SharedContentController';
|
|
14
14
|
export type { Exercise } from './controller/ExerciseController';
|
|
15
|
-
export type { UserInfo, Language } from './controller/SettingsController';
|
|
15
|
+
export type { UserInfo, Language, UserRole } from './controller/SettingsController';
|
|
16
16
|
export type { Message, ToolInvocation } from './controller/AIController';
|
|
17
17
|
export type { TriggerAction } from './controller/ExerciseController';
|
|
18
18
|
export type { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from './controller/AccomplishmentController';
|
|
19
|
+
export type { EventBusMessage } from './fromRimori/EventBus';
|
|
@@ -33,6 +33,17 @@ export interface RimoriInfo {
|
|
|
33
33
|
mainPanelPlugin?: ActivePlugin;
|
|
34
34
|
sidePanelPlugin?: ActivePlugin;
|
|
35
35
|
interfaceLanguage: string;
|
|
36
|
+
/**
|
|
37
|
+
* The release channel of the plugin installation.
|
|
38
|
+
*/
|
|
39
|
+
releaseChannel: 'alpha' | 'beta' | 'stable';
|
|
40
|
+
/**
|
|
41
|
+
* The database schema to use for plugin tables.
|
|
42
|
+
* Determined by rimori-main based on release channel:
|
|
43
|
+
* - 'plugins_alpha' for alpha release channel
|
|
44
|
+
* - 'plugins' for beta and stable release channels
|
|
45
|
+
*/
|
|
46
|
+
dbSchema: 'plugins' | 'plugins_alpha';
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
export class RimoriCommunicationHandler {
|
|
@@ -56,16 +67,18 @@ export class RimoriCommunicationHandler {
|
|
|
56
67
|
|
|
57
68
|
private initMessageChannel(worker = false): void {
|
|
58
69
|
const listener = (event: MessageEvent) => {
|
|
59
|
-
console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
70
|
+
// console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
60
71
|
const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
|
|
61
72
|
const [transferredPort] = event.ports || [];
|
|
62
73
|
|
|
63
74
|
if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
|
|
64
|
-
console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
// console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
76
|
+
// type,
|
|
77
|
+
// pluginId,
|
|
78
|
+
// currentPluginId: this.pluginId,
|
|
79
|
+
// hasPortProperty: !!transferredPort,
|
|
80
|
+
// event
|
|
81
|
+
// });
|
|
69
82
|
return;
|
|
70
83
|
}
|
|
71
84
|
|
|
@@ -218,7 +231,7 @@ export class RimoriCommunicationHandler {
|
|
|
218
231
|
} else {
|
|
219
232
|
// In main thread context, use EventBus
|
|
220
233
|
const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
|
|
221
|
-
console.log({ data });
|
|
234
|
+
// console.log({ data });
|
|
222
235
|
this.rimoriInfo = data;
|
|
223
236
|
this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
|
|
224
237
|
accessToken: () => Promise.resolve(this.getToken()),
|
package/src/plugin/Logger.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RimoriClient } from './RimoriClient';
|
|
2
|
-
import html2canvas from 'html2canvas';
|
|
3
2
|
|
|
4
3
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
5
4
|
|
|
@@ -307,16 +306,29 @@ export class Logger {
|
|
|
307
306
|
|
|
308
307
|
/**
|
|
309
308
|
* Capture a screenshot of the current page.
|
|
309
|
+
* Dynamically imports html2canvas only in browser environments.
|
|
310
310
|
* @returns Promise resolving to base64 screenshot or null if failed
|
|
311
311
|
*/
|
|
312
312
|
private async captureScreenshot(): Promise<string | null> {
|
|
313
|
-
|
|
313
|
+
// Only attempt to capture screenshot in browser environments
|
|
314
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
// Dynamically import html2canvas only when window is available
|
|
320
|
+
// html2canvas is an optional peer dependency - provided by @rimori/react-client
|
|
321
|
+
// In worker builds, this import should be marked as external to prevent bundling
|
|
322
|
+
const html2canvas = (await import('html2canvas')).default;
|
|
314
323
|
const canvas = await html2canvas(document.body);
|
|
315
324
|
const screenshot = canvas.toDataURL('image/png');
|
|
316
325
|
// this.originalConsole.log("screenshot captured", screenshot)
|
|
317
326
|
return screenshot;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
// html2canvas may not be available (e.g., in workers or when not installed)
|
|
329
|
+
// Silently fail to avoid breaking logging functionality
|
|
330
|
+
return null;
|
|
318
331
|
}
|
|
319
|
-
return null;
|
|
320
332
|
}
|
|
321
333
|
|
|
322
334
|
/**
|
|
@@ -39,11 +39,12 @@ export class RimoriClient {
|
|
|
39
39
|
this.rimoriInfo = info;
|
|
40
40
|
this.superbase = supabase;
|
|
41
41
|
this.pluginController = controller;
|
|
42
|
-
this.exerciseController = new ExerciseController(supabase);
|
|
42
|
+
this.exerciseController = new ExerciseController(supabase, this);
|
|
43
43
|
this.accomplishmentHandler = new AccomplishmentController(info.pluginId);
|
|
44
44
|
this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
|
|
45
45
|
this.sharedContentController = new SharedContentController(supabase, this);
|
|
46
|
-
|
|
46
|
+
const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
|
|
47
|
+
this.translator = new Translator(info.interfaceLanguage, currentPlugin?.endpoint || '');
|
|
47
48
|
|
|
48
49
|
//only init logger in workers and on main plugin pages
|
|
49
50
|
if (this.getQueryParam('applicationMode') !== 'sidebar') {
|
|
@@ -54,6 +55,11 @@ export class RimoriClient {
|
|
|
54
55
|
public get plugin() {
|
|
55
56
|
return {
|
|
56
57
|
pluginId: this.rimoriInfo.pluginId,
|
|
58
|
+
/**
|
|
59
|
+
* The release channel of this plugin installation.
|
|
60
|
+
* Determines which database schema is used for plugin tables.
|
|
61
|
+
*/
|
|
62
|
+
releaseChannel: this.rimoriInfo.releaseChannel,
|
|
57
63
|
/**
|
|
58
64
|
* Set the settings for the plugin.
|
|
59
65
|
* @param settings The settings to set.
|
|
@@ -122,7 +128,16 @@ export class RimoriClient {
|
|
|
122
128
|
from: <ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
|
|
123
129
|
relation: string,
|
|
124
130
|
): PostgrestQueryBuilder<GenericSchema, View, ViewName> => {
|
|
125
|
-
|
|
131
|
+
const tableName = this.db.getTableName(relation);
|
|
132
|
+
// Use the schema determined by rimori-main based on release channel
|
|
133
|
+
// Global tables (starting with 'global_') remain in public schema
|
|
134
|
+
// Plugin tables use the schema provided by rimori-main (plugins or plugins_alpha)
|
|
135
|
+
if (relation.startsWith('global_')) {
|
|
136
|
+
// Global tables stay in public schema
|
|
137
|
+
return this.superbase.from(tableName);
|
|
138
|
+
}
|
|
139
|
+
// Plugin tables go to the schema provided by rimori-main
|
|
140
|
+
return this.superbase.schema(this.rimoriInfo.dbSchema).from(tableName);
|
|
126
141
|
},
|
|
127
142
|
// storage: this.superbase.storage,
|
|
128
143
|
// functions: this.superbase.functions,
|
|
@@ -130,6 +145,13 @@ export class RimoriClient {
|
|
|
130
145
|
* The table prefix for of database tables of the plugin.
|
|
131
146
|
*/
|
|
132
147
|
tablePrefix: this.rimoriInfo.tablePrefix,
|
|
148
|
+
/**
|
|
149
|
+
* The database schema used for plugin tables.
|
|
150
|
+
* Determined by rimori-main based on release channel:
|
|
151
|
+
* - 'plugins_alpha' for alpha release channel
|
|
152
|
+
* - 'plugins' for beta and stable release channels
|
|
153
|
+
*/
|
|
154
|
+
schema: this.rimoriInfo.dbSchema,
|
|
133
155
|
/**
|
|
134
156
|
* Get the table name for a given plugin table.
|
|
135
157
|
* Internally all tables are prefixed with the plugin id. This function is used to get the correct table name for a given public table.
|
|
@@ -243,9 +265,9 @@ export class RimoriClient {
|
|
|
243
265
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
244
266
|
this.event.emit('action.requestMain');
|
|
245
267
|
this.event.on<MainPanelAction>('action.requestMain', ({ data }) => {
|
|
246
|
-
console.log('Received action ' + data.
|
|
247
|
-
console.log('Listening to actions', listeningActions);
|
|
248
|
-
if (
|
|
268
|
+
// console.log('Received action for main panel ' + data.action_key);
|
|
269
|
+
// console.log('Listening to actions', listeningActions);
|
|
270
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action_key)) {
|
|
249
271
|
callback(data);
|
|
250
272
|
}
|
|
251
273
|
});
|
|
@@ -256,9 +278,10 @@ export class RimoriClient {
|
|
|
256
278
|
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
257
279
|
this.event.emit('action.requestSidebar');
|
|
258
280
|
this.event.on<MainPanelAction>('action.requestSidebar', ({ data }) => {
|
|
259
|
-
console.log(
|
|
260
|
-
console.log('
|
|
261
|
-
|
|
281
|
+
// console.log("eventHandler .onSidePanelAction", data);
|
|
282
|
+
// console.log('Received action for sidebar ' + data.action);
|
|
283
|
+
// console.log('Listening to actions', listeningActions);
|
|
284
|
+
if (listeningActions.length === 0 || listeningActions.includes(data.action)) {
|
|
262
285
|
callback(data);
|
|
263
286
|
}
|
|
264
287
|
});
|