@rimori/client 1.3.1 → 1.4.3

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.
Files changed (148) hide show
  1. package/.prettierignore +35 -0
  2. package/README.md +77 -71
  3. package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
  4. package/dist/cli/scripts/init/dev-registration.js +4 -4
  5. package/dist/cli/scripts/init/main.js +1 -1
  6. package/dist/cli/scripts/init/package-setup.d.ts +1 -1
  7. package/dist/cli/scripts/init/package-setup.js +3 -3
  8. package/dist/cli/scripts/init/router-transformer.js +19 -12
  9. package/dist/cli/scripts/init/vite-config.d.ts +2 -2
  10. package/dist/cli/scripts/init/vite-config.js +2 -2
  11. package/dist/cli/scripts/release/release-config-upload.js +9 -9
  12. package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
  13. package/dist/cli/scripts/release/release-db-update.js +9 -9
  14. package/dist/cli/scripts/release/release-file-upload.js +2 -2
  15. package/dist/cli/scripts/release/release.js +2 -2
  16. package/dist/cli/types/DatabaseTypes.d.ts +2 -2
  17. package/dist/components/CRUDModal.d.ts +1 -1
  18. package/dist/components/CRUDModal.js +3 -3
  19. package/dist/components/MarkdownEditor.js +16 -16
  20. package/dist/components/Spinner.js +2 -2
  21. package/dist/components/ai/Assistant.js +7 -8
  22. package/dist/components/ai/Avatar.d.ts +2 -2
  23. package/dist/components/ai/Avatar.js +14 -7
  24. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
  25. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
  26. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
  27. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
  28. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
  29. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +1 -1
  30. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
  31. package/dist/components/audio/Playbutton.js +10 -7
  32. package/dist/components/components/ContextMenu.d.ts +1 -1
  33. package/dist/components/components/ContextMenu.js +19 -16
  34. package/dist/components.d.ts +10 -10
  35. package/dist/components.js +10 -10
  36. package/dist/core/controller/AIController.d.ts +2 -2
  37. package/dist/core/controller/AIController.js +20 -18
  38. package/dist/core/controller/ExerciseController.d.ts +52 -0
  39. package/dist/core/controller/ExerciseController.js +73 -0
  40. package/dist/core/controller/ObjectController.js +5 -5
  41. package/dist/core/controller/SettingsController.d.ts +22 -7
  42. package/dist/core/controller/SettingsController.js +73 -8
  43. package/dist/core/controller/SharedContentController.d.ts +3 -3
  44. package/dist/core/controller/SharedContentController.js +38 -20
  45. package/dist/core/controller/VoiceController.js +6 -4
  46. package/dist/core/core.d.ts +15 -14
  47. package/dist/core/core.js +7 -7
  48. package/dist/fromRimori/EventBus.js +23 -23
  49. package/dist/fromRimori/PluginTypes.d.ts +4 -4
  50. package/dist/hooks/UseChatHook.d.ts +3 -3
  51. package/dist/hooks/UseChatHook.js +9 -3
  52. package/dist/index.d.ts +10 -10
  53. package/dist/index.js +9 -9
  54. package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
  55. package/dist/plugin/AccomplishmentHandler.js +31 -27
  56. package/dist/plugin/AudioController.d.ts +1 -1
  57. package/dist/plugin/AudioController.js +6 -6
  58. package/dist/plugin/Logger.d.ts +5 -0
  59. package/dist/plugin/Logger.js +65 -13
  60. package/dist/plugin/PluginController.d.ts +7 -1
  61. package/dist/plugin/PluginController.js +32 -27
  62. package/dist/plugin/RimoriClient.d.ts +39 -14
  63. package/dist/plugin/RimoriClient.js +60 -31
  64. package/dist/plugin/StandaloneClient.d.ts +1 -1
  65. package/dist/plugin/StandaloneClient.js +35 -16
  66. package/dist/plugin/ThemeSetter.js +4 -4
  67. package/dist/providers/PluginProvider.js +44 -14
  68. package/dist/utils/Language.js +57 -57
  69. package/dist/utils/PluginUtils.js +3 -3
  70. package/dist/utils/difficultyConverter.d.ts +1 -1
  71. package/dist/utils/difficultyConverter.js +1 -1
  72. package/dist/utils/endpoint.js +2 -2
  73. package/dist/worker/WorkerSetup.d.ts +1 -1
  74. package/dist/worker/WorkerSetup.js +6 -6
  75. package/eslint.config.js +53 -0
  76. package/example/docs/devdocs.md +50 -40
  77. package/example/docs/overview.md +1 -1
  78. package/example/docs/userdocs.md +4 -1
  79. package/example/rimori.config.ts +51 -49
  80. package/example/worker/vite.config.ts +3 -3
  81. package/example/worker/worker.ts +2 -2
  82. package/package.json +17 -4
  83. package/prettier.config.js +8 -0
  84. package/src/cli/scripts/init/dev-registration.ts +5 -8
  85. package/src/cli/scripts/init/env-setup.ts +1 -1
  86. package/src/cli/scripts/init/file-operations.ts +1 -1
  87. package/src/cli/scripts/init/html-cleaner.ts +2 -5
  88. package/src/cli/scripts/init/main.ts +16 -13
  89. package/src/cli/scripts/init/package-setup.ts +11 -15
  90. package/src/cli/scripts/init/router-transformer.ts +40 -37
  91. package/src/cli/scripts/init/tailwind-config.ts +17 -26
  92. package/src/cli/scripts/init/vite-config.ts +3 -3
  93. package/src/cli/scripts/release/release-config-upload.ts +11 -11
  94. package/src/cli/scripts/release/release-db-update.ts +12 -12
  95. package/src/cli/scripts/release/release-file-upload.ts +3 -3
  96. package/src/cli/scripts/release/release.ts +4 -4
  97. package/src/cli/types/DatabaseTypes.ts +7 -8
  98. package/src/components/CRUDModal.tsx +64 -48
  99. package/src/components/MarkdownEditor.tsx +58 -27
  100. package/src/components/Spinner.tsx +24 -17
  101. package/src/components/ai/Assistant.tsx +70 -70
  102. package/src/components/ai/Avatar.tsx +20 -16
  103. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
  104. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
  105. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
  106. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +177 -178
  107. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
  108. package/src/components/ai/utils.ts +4 -4
  109. package/src/components/audio/Playbutton.tsx +101 -93
  110. package/src/components/components/ContextMenu.tsx +47 -35
  111. package/src/components.ts +10 -10
  112. package/src/core/controller/AIController.ts +62 -50
  113. package/src/core/controller/ExerciseController.ts +98 -0
  114. package/src/core/controller/ObjectController.ts +15 -10
  115. package/src/core/controller/SettingsController.ts +89 -16
  116. package/src/core/controller/SharedContentController.ts +80 -44
  117. package/src/core/controller/VoiceController.ts +10 -8
  118. package/src/core/core.ts +15 -15
  119. package/src/fromRimori/EventBus.ts +76 -47
  120. package/src/fromRimori/PluginTypes.ts +26 -17
  121. package/src/fromRimori/readme.md +2 -2
  122. package/src/hooks/UseChatHook.ts +25 -15
  123. package/src/index.ts +10 -10
  124. package/src/plugin/AccomplishmentHandler.ts +53 -35
  125. package/src/plugin/AudioController.ts +18 -12
  126. package/src/plugin/Logger.ts +77 -19
  127. package/src/plugin/PluginController.ts +60 -44
  128. package/src/plugin/RimoriClient.ts +133 -69
  129. package/src/plugin/StandaloneClient.ts +51 -24
  130. package/src/plugin/ThemeSetter.ts +5 -5
  131. package/src/providers/PluginProvider.tsx +90 -36
  132. package/src/style.scss +3 -3
  133. package/src/utils/Language.ts +58 -58
  134. package/src/utils/PluginUtils.ts +16 -20
  135. package/src/utils/difficultyConverter.ts +2 -2
  136. package/src/utils/endpoint.ts +3 -2
  137. package/src/worker/WorkerSetup.ts +8 -9
  138. package/tsconfig.json +2 -4
  139. package/dist/components/LoggerExample.d.ts +0 -6
  140. package/dist/components/LoggerExample.js +0 -79
  141. package/dist/core/controller/AudioController.d.ts +0 -0
  142. package/dist/core/controller/AudioController.js +0 -1
  143. package/dist/hooks/UseLogger.d.ts +0 -30
  144. package/dist/hooks/UseLogger.js +0 -122
  145. package/dist/plugin/LoggerExample.d.ts +0 -16
  146. package/dist/plugin/LoggerExample.js +0 -140
  147. package/dist/utils/audioFormats.d.ts +0 -26
  148. package/dist/utils/audioFormats.js +0 -67
@@ -2,7 +2,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
2
  import { UserInfo } from '../core/controller/SettingsController';
3
3
  import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
4
4
  import { ActivePlugin, Plugin } from '../fromRimori/PluginTypes';
5
- import { RimoriClient } from "./RimoriClient";
5
+ import { RimoriClient } from './RimoriClient';
6
6
  import { StandaloneClient } from './StandaloneClient';
7
7
  import { setTheme } from './ThemeSetter';
8
8
  import { Logger } from './Logger';
@@ -10,18 +10,25 @@ import { Logger } from './Logger';
10
10
  // Add declaration for WorkerGlobalScope
11
11
  declare const WorkerGlobalScope: any;
12
12
 
13
+ export interface Guild {
14
+ id: string;
15
+ longTermGoalOverride: string;
16
+ allowUserPluginSettings: boolean;
17
+ }
18
+
13
19
  export interface RimoriInfo {
14
- url: string,
15
- key: string,
16
- backendUrl: string,
17
- token: string,
18
- expiration: Date,
19
- tablePrefix: string,
20
- pluginId: string
21
- installedPlugins: Plugin[]
22
- profile: UserInfo
23
- mainPanelPlugin?: ActivePlugin
24
- sidePanelPlugin?: ActivePlugin
20
+ url: string;
21
+ key: string;
22
+ backendUrl: string;
23
+ token: string;
24
+ expiration: Date;
25
+ tablePrefix: string;
26
+ pluginId: string;
27
+ guild: Guild;
28
+ installedPlugins: Plugin[];
29
+ profile: UserInfo;
30
+ mainPanelPlugin?: ActivePlugin;
31
+ sidePanelPlugin?: ActivePlugin;
25
32
  }
26
33
 
27
34
  export class PluginController {
@@ -54,12 +61,16 @@ export class PluginController {
54
61
 
55
62
  private initMessageChannel(worker: boolean = false) {
56
63
  const listener = (event: MessageEvent) => {
57
- console.log("[PluginController] window message", { origin: event.origin, data: event.data });
64
+ console.log('[PluginController] window message', { origin: event.origin, data: event.data });
58
65
  const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
59
66
  const [transferredPort] = event.ports || [];
60
67
 
61
- if (type !== "rimori:init" || !transferredPort || pluginId !== this.pluginId) {
62
- console.log("[PluginController] message ignored (not init or wrong plugin)", { type, pluginId, hasPort: !!transferredPort });
68
+ if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
69
+ console.log('[PluginController] message ignored (not init or wrong plugin)', {
70
+ type,
71
+ pluginId,
72
+ hasPort: !!transferredPort,
73
+ });
63
74
  return;
64
75
  }
65
76
 
@@ -70,7 +81,7 @@ export class PluginController {
70
81
  if (rimoriInfo) {
71
82
  this.rimoriInfo = rimoriInfo;
72
83
  this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
73
- accessToken: () => Promise.resolve(rimoriInfo.token)
84
+ accessToken: () => Promise.resolve(rimoriInfo.token),
74
85
  });
75
86
  }
76
87
 
@@ -98,8 +109,8 @@ export class PluginController {
98
109
  }
99
110
 
100
111
  // Forward plugin events to parent (only after MessageChannel is ready)
101
- EventBus.on("*", (ev) => {
102
- if (ev.sender === this.pluginId && !ev.topic.startsWith("self.")) {
112
+ EventBus.on('*', (ev) => {
113
+ if (ev.sender === this.pluginId && !ev.topic.startsWith('self.')) {
103
114
  this.port?.postMessage({ event: ev });
104
115
  }
105
116
  });
@@ -108,27 +119,27 @@ export class PluginController {
108
119
  this.isMessageChannelReady = true;
109
120
 
110
121
  // Process any pending requests
111
- this.pendingRequests.forEach(request => request());
122
+ this.pendingRequests.forEach((request) => request());
112
123
  this.pendingRequests = [];
113
124
  };
114
125
  if (worker) {
115
126
  self.onmessage = listener;
116
127
  } else {
117
- window.addEventListener("message", listener);
128
+ window.addEventListener('message', listener);
118
129
  }
119
130
  this.sendHello(worker);
120
131
  }
121
132
 
122
133
  private sendHello(isWorker: boolean = false) {
123
134
  try {
124
- const payload = { type: "rimori:hello", pluginId: this.pluginId };
135
+ const payload = { type: 'rimori:hello', pluginId: this.pluginId };
125
136
  if (isWorker) {
126
137
  self.postMessage(payload);
127
138
  } else {
128
- window.parent.postMessage(payload, "*");
139
+ window.parent.postMessage(payload, '*');
129
140
  }
130
141
  } catch (e) {
131
- console.error("[PluginController] Error sending hello:", e);
142
+ console.error('[PluginController] Error sending hello:', e);
132
143
  }
133
144
  }
134
145
 
@@ -141,7 +152,7 @@ export class PluginController {
141
152
  PluginController.client = await RimoriClient.getInstance(PluginController.instance);
142
153
 
143
154
  //only init logger in workers and on main plugin pages
144
- if (PluginController.instance.getQueryParam("applicationMode") !== "sidebar") {
155
+ if (PluginController.instance.getQueryParam('applicationMode') !== 'sidebar') {
145
156
  Logger.getInstance(PluginController.client);
146
157
  }
147
158
  }
@@ -152,7 +163,7 @@ export class PluginController {
152
163
  return this.queryParams[key] || null;
153
164
  }
154
165
 
155
- public async getClient(): Promise<{ supabase: SupabaseClient, info: RimoriInfo }> {
166
+ public async getClient(): Promise<{ supabase: SupabaseClient; info: RimoriInfo }> {
156
167
  // Return cached client if valid
157
168
  if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
158
169
  return { supabase: this.supabase, info: this.rimoriInfo };
@@ -160,7 +171,7 @@ export class PluginController {
160
171
 
161
172
  // If MessageChannel is not ready yet, queue the request
162
173
  if (!this.isMessageChannelReady) {
163
- return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
174
+ return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
164
175
  this.pendingRequests.push(async () => {
165
176
  const result = await this.getClient();
166
177
  resolve(result);
@@ -185,18 +196,18 @@ export class PluginController {
185
196
  sender: this.pluginId,
186
197
  topic: 'global.supabase.requestAccess',
187
198
  data: {},
188
- debug: false
189
- }
199
+ debug: false,
200
+ },
190
201
  };
191
202
 
192
- return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
203
+ return new Promise<{ supabase: SupabaseClient; info: RimoriInfo }>((resolve) => {
193
204
  // Listen for the response
194
205
  const originalOnMessage = self.onmessage;
195
206
  self.onmessage = (event) => {
196
207
  if (event.data?.topic === 'global.supabase.requestAccess' && event.data?.eventId === eventId) {
197
208
  this.rimoriInfo = event.data.data;
198
209
  this.supabase = createClient(this.rimoriInfo!.url, this.rimoriInfo!.key, {
199
- accessToken: () => Promise.resolve(this.getToken())
210
+ accessToken: () => Promise.resolve(this.getToken()),
200
211
  });
201
212
  self.onmessage = originalOnMessage; // Restore original handler
202
213
  resolve({ supabase: this.supabase, info: this.rimoriInfo! });
@@ -210,10 +221,11 @@ export class PluginController {
210
221
  });
211
222
  } else {
212
223
  // In main thread context, use EventBus
213
- const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
224
+ const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
225
+ console.log({ data });
214
226
  this.rimoriInfo = data;
215
227
  this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
216
- accessToken: () => Promise.resolve(this.getToken())
228
+ accessToken: () => Promise.resolve(this.getToken()),
217
229
  });
218
230
  }
219
231
  }
@@ -228,13 +240,16 @@ export class PluginController {
228
240
 
229
241
  // If we don't have rimoriInfo, request it
230
242
  if (!this.rimoriInfo) {
231
- const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
243
+ const { data } = await EventBus.request<RimoriInfo>(this.pluginId, 'global.supabase.requestAccess');
232
244
  this.rimoriInfo = data;
233
245
  return this.rimoriInfo.token;
234
246
  }
235
247
 
236
248
  // If token is expired, request fresh access
237
- const { data } = await EventBus.request<{ token: string, expiration: Date }>(this.pluginId, "global.supabase.requestAccess");
249
+ const { data } = await EventBus.request<{ token: string; expiration: Date }>(
250
+ this.pluginId,
251
+ 'global.supabase.requestAccess',
252
+ );
238
253
  this.rimoriInfo.token = data.token;
239
254
  this.rimoriInfo.expiration = data.expiration;
240
255
 
@@ -248,7 +263,7 @@ export class PluginController {
248
263
  */
249
264
  public getSupabaseUrl() {
250
265
  if (!this.rimoriInfo) {
251
- throw new Error("Supabase info not found");
266
+ throw new Error('Supabase info not found');
252
267
  }
253
268
 
254
269
  return this.rimoriInfo.url;
@@ -256,30 +271,31 @@ export class PluginController {
256
271
 
257
272
  public getBackendUrl() {
258
273
  if (!this.rimoriInfo) {
259
- throw new Error("Rimori info not found");
274
+ throw new Error('Rimori info not found');
260
275
  }
261
276
  return this.rimoriInfo.backendUrl;
262
277
  }
263
278
 
264
279
  public getGlobalEventTopic(preliminaryTopic: string) {
265
- if (preliminaryTopic.startsWith("global.")) {
280
+ if (preliminaryTopic.startsWith('global.')) {
266
281
  return preliminaryTopic;
267
282
  }
268
- if (preliminaryTopic.startsWith("self.")) {
283
+ if (preliminaryTopic.startsWith('self.')) {
269
284
  return preliminaryTopic;
270
285
  }
271
- const topicParts = preliminaryTopic.split(".");
286
+ const topicParts = preliminaryTopic.split('.');
272
287
  if (topicParts.length === 3) {
273
- if (!topicParts[0].startsWith("pl") && topicParts[0] !== "global") {
288
+ if (!topicParts[0].startsWith('pl') && topicParts[0] !== 'global') {
274
289
  throw new Error("The event topic must start with the plugin id or 'global'.");
275
290
  }
276
291
  return preliminaryTopic;
277
292
  } else if (topicParts.length > 3) {
278
- throw new Error(`The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`);
293
+ throw new Error(
294
+ `The event topic must consist of 3 parts. <pluginId>.<topic area>.<action>. Received: ${preliminaryTopic}`,
295
+ );
279
296
  }
280
297
 
281
- const topicRoot = this.rimoriInfo?.pluginId ?? "global";
298
+ const topicRoot = this.rimoriInfo?.pluginId ?? 'global';
282
299
  return `${topicRoot}.${preliminaryTopic}`;
283
300
  }
284
-
285
- }
301
+ }
@@ -1,21 +1,30 @@
1
- import { PostgrestQueryBuilder } from "@supabase/postgrest-js";
2
- import { SupabaseClient } from "@supabase/supabase-js";
3
- import { GenericSchema } from "@supabase/supabase-js/dist/module/lib/types";
4
- import { generateText, Message, OnLLMResponse, streamChatGPT } from "../core/controller/AIController";
5
- import { generateObject, ObjectRequest } from "../core/controller/ObjectController";
6
- import { SettingsController, UserInfo } from "../core/controller/SettingsController";
7
- import { SharedContent, SharedContentController, SharedContentFilter, SharedContentObjectRequest } from "../core/controller/SharedContentController";
8
- import { getSTTResponse, getTTSResponse } from "../core/controller/VoiceController";
9
- import { EventBus, EventBusMessage, EventHandler, EventPayload } from "../fromRimori/EventBus";
10
- import { ActivePlugin, MainPanelAction, Plugin, Tool } from "../fromRimori/PluginTypes";
11
- import { AccomplishmentHandler, AccomplishmentPayload } from "./AccomplishmentHandler";
12
- import { PluginController, RimoriInfo } from "./PluginController";
13
-
1
+ import { PostgrestQueryBuilder } from '@supabase/postgrest-js';
2
+ import { SupabaseClient } from '@supabase/supabase-js';
3
+ import { GenericSchema } from '@supabase/supabase-js/dist/module/lib/types';
4
+ import { generateText, Message, OnLLMResponse, streamChatGPT } from '../core/controller/AIController';
5
+ import { generateObject, ObjectRequest } from '../core/controller/ObjectController';
6
+ import { SettingsController, UserInfo } from '../core/controller/SettingsController';
7
+ import {
8
+ SharedContent,
9
+ SharedContentController,
10
+ SharedContentFilter,
11
+ SharedContentObjectRequest,
12
+ } from '../core/controller/SharedContentController';
13
+ import { getSTTResponse, getTTSResponse } from '../core/controller/VoiceController';
14
+ import { ExerciseController, CreateExerciseParams } from '../core/controller/ExerciseController';
15
+ import { EventBus, EventBusMessage, EventHandler, EventPayload } from '../fromRimori/EventBus';
16
+ import { ActivePlugin, MainPanelAction, Plugin, Tool } from '../fromRimori/PluginTypes';
17
+ import { AccomplishmentHandler, AccomplishmentPayload } from './AccomplishmentHandler';
18
+ import { PluginController, RimoriInfo } from './PluginController';
14
19
 
15
20
  interface Db {
16
21
  from: {
17
- <TableName extends string & keyof GenericSchema['Tables'], Table extends GenericSchema['Tables'][TableName]>(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, TableName>;
18
- <ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(relation: ViewName): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
22
+ <TableName extends string & keyof GenericSchema['Tables'], Table extends GenericSchema['Tables'][TableName]>(
23
+ relation: TableName,
24
+ ): PostgrestQueryBuilder<GenericSchema, Table, TableName>;
25
+ <ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
26
+ relation: ViewName,
27
+ ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
19
28
  };
20
29
  // storage: SupabaseClient["storage"];
21
30
 
@@ -40,7 +49,7 @@ interface PluginInterface {
40
49
  * Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
41
50
  * @param defaultSettings The default settings to use if no settings are found.
42
51
  * @param genericSettings The type of settings to get.
43
- * @returns The settings for the plugin.
52
+ * @returns The settings for the plugin.
44
53
  */
45
54
  getSettings: <T extends object>(defaultSettings: T) => Promise<T>;
46
55
  /**
@@ -53,15 +62,15 @@ interface PluginInterface {
53
62
  /**
54
63
  * All installed plugins.
55
64
  */
56
- installedPlugins: Plugin[],
65
+ installedPlugins: Plugin[];
57
66
  /**
58
67
  * The plugin that is loaded in the main panel.
59
68
  */
60
- mainPanelPlugin?: ActivePlugin,
69
+ mainPanelPlugin?: ActivePlugin;
61
70
  /**
62
71
  * The plugin that is loaded in the side panel.
63
72
  */
64
- sidePanelPlugin?: ActivePlugin,
73
+ sidePanelPlugin?: ActivePlugin;
65
74
  };
66
75
  getUserInfo: () => UserInfo;
67
76
  }
@@ -72,6 +81,7 @@ export class RimoriClient {
72
81
  private pluginController: PluginController;
73
82
  private settingsController: SettingsController;
74
83
  private sharedContentController: SharedContentController;
84
+ private exerciseController: ExerciseController;
75
85
  private accomplishmentHandler: AccomplishmentHandler;
76
86
  private rimoriInfo: RimoriInfo;
77
87
  public plugin: PluginInterface;
@@ -81,19 +91,21 @@ export class RimoriClient {
81
91
  this.rimoriInfo = info;
82
92
  this.superbase = supabase;
83
93
  this.pluginController = pluginController;
84
- this.settingsController = new SettingsController(supabase, info.pluginId);
94
+ this.settingsController = new SettingsController(supabase, info.pluginId, info.guild);
85
95
  this.sharedContentController = new SharedContentController(this.superbase, this);
96
+ this.exerciseController = new ExerciseController(supabase, pluginController);
86
97
  this.accomplishmentHandler = new AccomplishmentHandler(info.pluginId);
87
98
 
88
99
  this.from = this.from.bind(this);
100
+ this.getTableName = this.getTableName.bind(this);
89
101
 
90
102
  this.db = {
91
103
  from: this.from,
92
104
  // storage: this.superbase.storage,
93
105
  // functions: this.superbase.functions,
94
106
  tablePrefix: info.tablePrefix,
95
- getTableName: this.getTableName.bind(this),
96
- }
107
+ getTableName: this.getTableName,
108
+ };
97
109
  this.plugin = {
98
110
  pluginId: info.pluginId,
99
111
  setSettings: async (settings: any) => {
@@ -110,14 +122,14 @@ export class RimoriClient {
110
122
  installedPlugins: this.rimoriInfo.installedPlugins,
111
123
  mainPanelPlugin: this.rimoriInfo.mainPanelPlugin,
112
124
  sidePanelPlugin: this.rimoriInfo.sidePanelPlugin,
113
- }
114
- }
115
- }
125
+ };
126
+ },
127
+ };
116
128
  }
117
129
 
118
130
  public event = {
119
131
  /**
120
- * Emit an event to Rimori or a plugin.
132
+ * Emit an event to Rimori or a plugin.
121
133
  * The topic schema is:
122
134
  * {pluginId}.{eventId}
123
135
  * Check out the event bus documentation for more information.
@@ -148,7 +160,10 @@ export class RimoriClient {
148
160
  */
149
161
  on: <T = EventPayload>(topic: string | string[], callback: EventHandler<T>) => {
150
162
  const topics = Array.isArray(topic) ? topic : [topic];
151
- return EventBus.on<T>(topics.map(t => this.pluginController.getGlobalEventTopic(t)), callback);
163
+ return EventBus.on<T>(
164
+ topics.map((t) => this.pluginController.getGlobalEventTopic(t)),
165
+ callback,
166
+ );
152
167
  },
153
168
  /**
154
169
  * Subscribe to an event once.
@@ -163,9 +178,16 @@ export class RimoriClient {
163
178
  * @param topic The topic to respond to.
164
179
  * @param data The data to respond with.
165
180
  */
166
- respond: <T = EventPayload>(topic: string | string[], data: EventPayload | ((data: EventBusMessage<T>) => EventPayload | Promise<EventPayload>)) => {
181
+ respond: <T = EventPayload>(
182
+ topic: string | string[],
183
+ data: EventPayload | ((data: EventBusMessage<T>) => EventPayload | Promise<EventPayload>),
184
+ ) => {
167
185
  const topics = Array.isArray(topic) ? topic : [topic];
168
- EventBus.respond(this.plugin.pluginId, topics.map(t => this.pluginController.getGlobalEventTopic(t)), data);
186
+ EventBus.respond(
187
+ this.plugin.pluginId,
188
+ topics.map((t) => this.pluginController.getGlobalEventTopic(t)),
189
+ data,
190
+ );
169
191
  },
170
192
  /**
171
193
  * Emit an accomplishment.
@@ -179,7 +201,10 @@ export class RimoriClient {
179
201
  * @param accomplishmentTopic The topic to subscribe to.
180
202
  * @param callback The callback to call when the accomplishment is emitted.
181
203
  */
182
- onAccomplishment: (accomplishmentTopic: string, callback: (payload: EventBusMessage<AccomplishmentPayload>) => void) => {
204
+ onAccomplishment: (
205
+ accomplishmentTopic: string,
206
+ callback: (payload: EventBusMessage<AccomplishmentPayload>) => void,
207
+ ) => {
183
208
  this.accomplishmentHandler.subscribe(accomplishmentTopic, callback);
184
209
  },
185
210
  /**
@@ -189,21 +214,21 @@ export class RimoriClient {
189
214
  * @param text Optional text to be used for the action like for example text that the translator would look up.
190
215
  */
191
216
  emitSidebarAction: (pluginId: string, actionKey: string, text?: string) => {
192
- this.event.emit("global.sidebar.triggerAction", { plugin_id: pluginId, action_key: actionKey, text });
217
+ this.event.emit('global.sidebar.triggerAction', { plugin_id: pluginId, action_key: actionKey, text });
193
218
  },
194
219
 
195
220
  onMainPanelAction: (callback: (data: MainPanelAction) => void) => {
196
221
  // this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
197
- this.event.emit("action.requestMain")
198
- this.event.on<MainPanelAction>("action.requestMain", ({ data }) => callback(data));
199
- }
200
- }
222
+ this.event.emit('action.requestMain');
223
+ this.event.on<MainPanelAction>('action.requestMain', ({ data }) => callback(data));
224
+ },
225
+ };
201
226
 
202
227
  public navigation = {
203
228
  toDashboard: () => {
204
- this.event.emit("global.navigation.triggerToDashboard");
205
- }
206
- }
229
+ this.event.emit('global.navigation.triggerToDashboard');
230
+ },
231
+ };
207
232
 
208
233
  /**
209
234
  * Get a query parameter value that was passed via MessageChannel
@@ -224,36 +249,37 @@ export class RimoriClient {
224
249
 
225
250
  private from<
226
251
  TableName extends string & keyof GenericSchema['Tables'],
227
- Table extends GenericSchema['Tables'][TableName]
228
- >(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, TableName>
229
- private from<
230
- ViewName extends string & keyof GenericSchema['Views'],
231
- View extends GenericSchema['Views'][ViewName]
232
- >(relation: ViewName): PostgrestQueryBuilder<GenericSchema, View, ViewName>
252
+ Table extends GenericSchema['Tables'][TableName],
253
+ >(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, TableName>;
254
+ private from<ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(
255
+ relation: ViewName,
256
+ ): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
233
257
  private from(relation: string): PostgrestQueryBuilder<GenericSchema, any, any> {
234
258
  return this.superbase.from(this.getTableName(relation));
235
259
  }
236
260
 
237
261
  private getTableName(table: string) {
238
262
  if (/[A-Z]/.test(table)) {
239
- throw new Error("Table name cannot include uppercase letters. Please use snake_case for table names.");
263
+ throw new Error('Table name cannot include uppercase letters. Please use snake_case for table names.');
240
264
  }
241
- if (table.startsWith("global_")) {
242
- return table.replace("global_", "");
265
+ if (table.startsWith('global_')) {
266
+ return table.replace('global_', '');
243
267
  }
244
- return this.db.tablePrefix + "_" + table;
268
+ return this.db.tablePrefix + '_' + table;
245
269
  }
246
270
 
247
271
  public ai = {
248
272
  getText: async (messages: Message[], tools?: Tool[]): Promise<string> => {
249
273
  const token = await this.pluginController.getToken();
250
- return generateText(this.pluginController.getBackendUrl(), messages, tools || [], token).then(({ messages }) => messages[0].content[0].text);
274
+ return generateText(this.pluginController.getBackendUrl(), messages, tools || [], token).then(
275
+ ({ messages }) => messages[0].content[0].text,
276
+ );
251
277
  },
252
278
  getSteamedText: async (messages: Message[], onMessage: OnLLMResponse, tools?: Tool[]) => {
253
279
  const token = await this.pluginController.getToken();
254
280
  streamChatGPT(this.pluginController.getBackendUrl(), messages, tools || [], onMessage, token);
255
281
  },
256
- getVoice: async (text: string, voice = "alloy", speed = 1, language?: string): Promise<Blob> => {
282
+ getVoice: async (text: string, voice = 'alloy', speed = 1, language?: string): Promise<Blob> => {
257
283
  const token = await this.pluginController.getToken();
258
284
  return getTTSResponse(this.pluginController.getBackendUrl(), { input: text, voice, speed, language }, token);
259
285
  },
@@ -266,7 +292,7 @@ export class RimoriClient {
266
292
  return generateObject(this.pluginController.getBackendUrl(), request, token);
267
293
  },
268
294
  // getSteamedObject: this.generateObjectStream,
269
- }
295
+ };
270
296
 
271
297
  public runtime = {
272
298
  fetchBackend: async (url: string, options: RequestInit) => {
@@ -275,11 +301,11 @@ export class RimoriClient {
275
301
  ...options,
276
302
  headers: {
277
303
  ...options.headers,
278
- 'Authorization': `Bearer ${token}`
279
- }
304
+ Authorization: `Bearer ${token}`,
305
+ },
280
306
  });
281
- }
282
- }
307
+ },
308
+ };
283
309
 
284
310
  public community = {
285
311
  /**
@@ -304,7 +330,11 @@ export class RimoriClient {
304
330
  * @param limit The optional limit for the number of results.
305
331
  * @returns The list of shared content items.
306
332
  */
307
- getList: async <T = any>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<SharedContent<T>[]> => {
333
+ getList: async <T = any>(
334
+ contentType: string,
335
+ filter?: SharedContentFilter,
336
+ limit?: number,
337
+ ): Promise<SharedContent<T>[]> => {
308
338
  return await this.sharedContentController.getSharedContentList(contentType, filter, limit);
309
339
  },
310
340
  /**
@@ -325,7 +355,12 @@ export class RimoriClient {
325
355
  filter?: SharedContentFilter,
326
356
  options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
327
357
  ): Promise<SharedContent<T>> => {
328
- return await this.sharedContentController.getNewSharedContent(contentType, generatorInstructions, filter, options);
358
+ return await this.sharedContentController.getNewSharedContent(
359
+ contentType,
360
+ generatorInstructions,
361
+ filter,
362
+ options,
363
+ );
329
364
  },
330
365
  /**
331
366
  * Create a new shared content item.
@@ -345,10 +380,10 @@ export class RimoriClient {
345
380
  return await this.sharedContentController.updateSharedContent(id, content);
346
381
  },
347
382
  /**
348
- * Complete a shared content item.
349
- * @param contentType The type of shared content to complete. E.g. assignments, exercises, etc.
350
- * @param assignmentId The id of the shared content item to complete.
351
- */
383
+ * Complete a shared content item.
384
+ * @param contentType The type of shared content to complete. E.g. assignments, exercises, etc.
385
+ * @param assignmentId The id of the shared content item to complete.
386
+ */
352
387
  complete: async (contentType: string, assignmentId: string) => {
353
388
  return await this.sharedContentController.completeSharedContent(contentType, assignmentId);
354
389
  },
@@ -358,11 +393,11 @@ export class RimoriClient {
358
393
  * Useful for marking content as completed, ongoing, hidden, liked, disliked, or bookmarked.
359
394
  */
360
395
  updateState: async (params: {
361
- contentType: string
362
- id: string
363
- state?: 'completed' | 'ongoing' | 'hidden'
364
- reaction?: 'liked' | 'disliked' | null
365
- bookmarked?: boolean
396
+ contentType: string;
397
+ id: string;
398
+ state?: 'completed' | 'ongoing' | 'hidden';
399
+ reaction?: 'liked' | 'disliked' | null;
400
+ bookmarked?: boolean;
366
401
  }): Promise<void> => {
367
402
  return await this.sharedContentController.updateSharedContentState(params);
368
403
  },
@@ -373,7 +408,36 @@ export class RimoriClient {
373
408
  */
374
409
  remove: async (id: string): Promise<SharedContent<any>> => {
375
410
  return await this.sharedContentController.removeSharedContent(id);
376
- }
377
- }
378
- }
411
+ },
412
+ },
413
+ };
414
+
415
+ public exercise = {
416
+ /**
417
+ * Fetches weekly exercises from the weekly_exercises view.
418
+ * Shows exercises for the current week that haven't expired.
419
+ * @returns Array of exercise objects.
420
+ */
421
+ view: async () => {
422
+ return this.exerciseController.viewWeeklyExercises();
423
+ },
424
+
425
+ /**
426
+ * Creates a new exercise via the backend API.
427
+ * @param params Exercise creation parameters.
428
+ * @returns Created exercise object.
429
+ */
430
+ add: async (params: CreateExerciseParams) => {
431
+ return this.exerciseController.addExercise(params);
432
+ },
433
+
434
+ /**
435
+ * Deletes an exercise via the backend API.
436
+ * @param id The exercise ID to delete.
437
+ * @returns Success status.
438
+ */
439
+ delete: async (id: string) => {
440
+ return this.exerciseController.deleteExercise(id);
441
+ },
442
+ };
379
443
  }