@rimori/client 1.1.10 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +189 -63
  2. package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
  3. package/dist/cli/scripts/init/dev-registration.js +174 -0
  4. package/dist/cli/scripts/init/env-setup.d.ts +9 -0
  5. package/dist/cli/scripts/init/env-setup.js +43 -0
  6. package/dist/cli/scripts/init/file-operations.d.ts +4 -0
  7. package/dist/cli/scripts/init/file-operations.js +51 -0
  8. package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
  9. package/dist/cli/scripts/init/html-cleaner.js +38 -0
  10. package/dist/cli/scripts/init/main.d.ts +2 -0
  11. package/dist/cli/scripts/init/main.js +160 -0
  12. package/dist/cli/scripts/init/package-setup.d.ts +32 -0
  13. package/dist/cli/scripts/init/package-setup.js +75 -0
  14. package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
  15. package/dist/cli/scripts/init/router-transformer.js +254 -0
  16. package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
  17. package/dist/cli/scripts/init/tailwind-config.js +56 -0
  18. package/dist/cli/scripts/init/vite-config.d.ts +20 -0
  19. package/dist/cli/scripts/init/vite-config.js +54 -0
  20. package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
  21. package/dist/cli/scripts/release/release-config-upload.js +116 -0
  22. package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
  23. package/dist/cli/scripts/release/release-db-update.js +100 -0
  24. package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
  25. package/dist/cli/scripts/release/release-file-upload.js +136 -0
  26. package/dist/cli/scripts/release/release.d.ts +23 -0
  27. package/dist/cli/scripts/release/release.js +70 -0
  28. package/dist/cli/types/DatabaseTypes.d.ts +103 -0
  29. package/dist/cli/types/DatabaseTypes.js +2 -0
  30. package/dist/components/LoggerExample.d.ts +6 -0
  31. package/dist/components/LoggerExample.js +79 -0
  32. package/dist/components/ai/Assistant.js +5 -5
  33. package/dist/components/ai/Avatar.d.ts +3 -2
  34. package/dist/components/ai/Avatar.js +11 -6
  35. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
  36. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
  37. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +48 -33
  38. package/dist/components/ai/utils.js +0 -1
  39. package/dist/components/audio/Playbutton.js +4 -4
  40. package/dist/{core → components}/components/ContextMenu.js +50 -11
  41. package/dist/components.d.ts +5 -5
  42. package/dist/components.js +5 -5
  43. package/dist/core/controller/AIController.d.ts +15 -0
  44. package/dist/core/controller/AIController.js +253 -0
  45. package/dist/core/controller/AudioController.d.ts +0 -0
  46. package/dist/core/controller/AudioController.js +1 -0
  47. package/dist/{controller → core/controller}/ObjectController.d.ts +10 -2
  48. package/dist/{controller → core/controller}/ObjectController.js +8 -8
  49. package/dist/{controller → core/controller}/SettingsController.d.ts +28 -4
  50. package/dist/{controller → core/controller}/SettingsController.js +0 -25
  51. package/dist/{controller → core/controller}/SharedContentController.d.ts +31 -3
  52. package/dist/{controller → core/controller}/SharedContentController.js +77 -26
  53. package/dist/core/controller/VoiceController.d.ts +9 -0
  54. package/dist/{controller → core/controller}/VoiceController.js +11 -4
  55. package/dist/core/core.d.ts +14 -0
  56. package/dist/core/core.js +8 -0
  57. package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
  58. package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +26 -9
  59. package/dist/fromRimori/PluginTypes.d.ts +174 -0
  60. package/dist/hooks/UseChatHook.d.ts +2 -1
  61. package/dist/hooks/UseChatHook.js +6 -4
  62. package/dist/hooks/UseLogger.d.ts +30 -0
  63. package/dist/hooks/UseLogger.js +122 -0
  64. package/dist/index.d.ts +6 -3
  65. package/dist/index.js +5 -3
  66. package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
  67. package/dist/plugin/AccomplishmentHandler.js +1 -1
  68. package/dist/plugin/AudioController.d.ts +37 -0
  69. package/dist/plugin/AudioController.js +68 -0
  70. package/dist/plugin/Logger.d.ts +68 -0
  71. package/dist/plugin/Logger.js +256 -0
  72. package/dist/plugin/LoggerExample.d.ts +16 -0
  73. package/dist/plugin/LoggerExample.js +140 -0
  74. package/dist/plugin/PluginController.d.ts +30 -5
  75. package/dist/plugin/PluginController.js +182 -53
  76. package/dist/plugin/RimoriClient.d.ts +68 -21
  77. package/dist/plugin/RimoriClient.js +88 -41
  78. package/dist/plugin/StandaloneClient.d.ts +1 -0
  79. package/dist/plugin/StandaloneClient.js +24 -10
  80. package/dist/plugin/ThemeSetter.d.ts +2 -1
  81. package/dist/plugin/ThemeSetter.js +13 -7
  82. package/dist/providers/PluginProvider.d.ts +4 -1
  83. package/dist/providers/PluginProvider.js +39 -13
  84. package/dist/utils/Language.d.ts +2 -1
  85. package/dist/utils/Language.js +4 -2
  86. package/dist/utils/audioFormats.d.ts +26 -0
  87. package/dist/utils/audioFormats.js +67 -0
  88. package/dist/utils/difficultyConverter.js +1 -1
  89. package/dist/utils/endpoint.d.ts +2 -0
  90. package/dist/utils/endpoint.js +2 -0
  91. package/dist/worker/WorkerSetup.d.ts +3 -2
  92. package/dist/worker/WorkerSetup.js +22 -65
  93. package/example/docs/devdocs.md +231 -0
  94. package/example/docs/overview.md +29 -0
  95. package/example/docs/userdocs.md +123 -0
  96. package/example/rimori.config.ts +89 -0
  97. package/example/worker/vite.config.ts +23 -0
  98. package/example/worker/worker.ts +11 -0
  99. package/package.json +16 -9
  100. package/src/cli/scripts/init/dev-registration.ts +192 -0
  101. package/src/cli/scripts/init/env-setup.ts +44 -0
  102. package/src/cli/scripts/init/file-operations.ts +58 -0
  103. package/src/cli/scripts/init/html-cleaner.ts +48 -0
  104. package/src/cli/scripts/init/main.ts +172 -0
  105. package/src/cli/scripts/init/package-setup.ts +117 -0
  106. package/src/cli/scripts/init/router-transformer.ts +329 -0
  107. package/src/cli/scripts/init/tailwind-config.ts +75 -0
  108. package/src/cli/scripts/init/vite-config.ts +73 -0
  109. package/src/cli/scripts/release/release-config-upload.ts +114 -0
  110. package/src/cli/scripts/release/release-db-update.ts +97 -0
  111. package/src/cli/scripts/release/release-file-upload.ts +138 -0
  112. package/src/cli/scripts/release/release.ts +69 -0
  113. package/src/cli/types/DatabaseTypes.ts +117 -0
  114. package/src/components/ai/Assistant.tsx +5 -5
  115. package/src/components/ai/Avatar.tsx +25 -8
  116. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
  117. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +50 -35
  118. package/src/components/ai/utils.ts +0 -2
  119. package/src/components/audio/Playbutton.tsx +4 -4
  120. package/src/{core → components}/components/ContextMenu.tsx +56 -12
  121. package/src/components.ts +6 -6
  122. package/src/core/controller/AIController.ts +283 -0
  123. package/src/core/controller/ObjectController.ts +115 -0
  124. package/src/{controller → core/controller}/SettingsController.ts +29 -29
  125. package/src/{controller → core/controller}/SharedContentController.ts +91 -29
  126. package/src/core/controller/VoiceController.ts +31 -0
  127. package/src/core/core.ts +16 -0
  128. package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +29 -11
  129. package/src/fromRimori/PluginTypes.ts +205 -0
  130. package/src/hooks/UseChatHook.ts +8 -5
  131. package/src/index.ts +6 -3
  132. package/src/plugin/AccomplishmentHandler.ts +1 -1
  133. package/src/plugin/AudioController.ts +58 -0
  134. package/src/plugin/Logger.ts +324 -0
  135. package/src/plugin/PluginController.ts +203 -63
  136. package/src/plugin/RimoriClient.ts +127 -55
  137. package/src/plugin/StandaloneClient.ts +30 -11
  138. package/src/plugin/ThemeSetter.ts +16 -9
  139. package/src/providers/PluginProvider.tsx +46 -13
  140. package/src/utils/Language.ts +4 -2
  141. package/src/utils/difficultyConverter.ts +3 -3
  142. package/src/utils/endpoint.ts +2 -0
  143. package/src/worker/WorkerSetup.ts +13 -60
  144. package/dist/components/PluginController.d.ts +0 -21
  145. package/dist/components/PluginController.js +0 -116
  146. package/dist/controller/AIController.d.ts +0 -23
  147. package/dist/controller/AIController.js +0 -93
  148. package/dist/controller/SidePluginController.d.ts +0 -3
  149. package/dist/controller/SidePluginController.js +0 -31
  150. package/dist/controller/VoiceController.d.ts +0 -10
  151. package/dist/core.d.ts +0 -7
  152. package/dist/core.js +0 -7
  153. package/dist/plugin/ContextMenu.d.ts +0 -17
  154. package/dist/plugin/ContextMenu.js +0 -45
  155. package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
  156. package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
  157. package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
  158. package/dist/providers/PluginController.d.ts +0 -21
  159. package/dist/providers/PluginController.js +0 -116
  160. package/dist/types/Actions.d.ts +0 -4
  161. package/dist/types/Actions.js +0 -1
  162. package/src/controller/AIController.ts +0 -112
  163. package/src/controller/ObjectController.ts +0 -107
  164. package/src/controller/SidePluginController.ts +0 -25
  165. package/src/controller/VoiceController.ts +0 -26
  166. package/src/core.ts +0 -8
  167. package/src/plugin/fromRimori/PluginTypes.ts +0 -64
  168. package/src/types/Actions.ts +0 -6
  169. /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
  170. /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
  171. /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
@@ -1,5 +1,5 @@
1
1
  import { SupabaseClient } from '@supabase/supabase-js';
2
- import { RimoriClient } from "../plugin/RimoriClient";
2
+ import { RimoriClient } from "../../plugin/RimoriClient";
3
3
  import { ObjectRequest } from "./ObjectController";
4
4
 
5
5
  export interface SharedContentObjectRequest extends ObjectRequest {
@@ -22,7 +22,11 @@ export class SharedContentController {
22
22
  * @param contentType - The type of content to fetch.
23
23
  * @param generatorInstructions - The instructions for the generator. The object needs to have a tool property with a topic and keywords property to let a new unique topic be generated.
24
24
  * @param filter - An optional filter to apply to the query.
25
- * @param privateTopic - An optional flag to indicate if the topic should be private and only be visible to the user.
25
+ * @param options - Optional options.
26
+ * @param options.privateTopic - If the topic should be private and only be visible to the user.
27
+ * @param options.skipDbSave - If true, do not persist a newly generated content to the DB (default false).
28
+ * @param options.alwaysGenerateNew - If true, always generate a new content even if there is already a content with the same filter.
29
+ * @param options.excludeIds - Optional list of shared_content ids to exclude from selection.
26
30
  * @returns The new shared content.
27
31
  */
28
32
  public async getNewSharedContent<T>(
@@ -30,56 +34,66 @@ export class SharedContentController {
30
34
  generatorInstructions: SharedContentObjectRequest,
31
35
  //this filter is there if the content should be filtered additionally by a column and value
32
36
  filter?: SharedContentFilter,
33
- privateTopic?: boolean,
37
+ options?: { privateTopic?: boolean, skipDbSave?: boolean, alwaysGenerateNew?: boolean, excludeIds?: string[] },
34
38
  ): Promise<SharedContent<T>> {
35
- const query = this.supabase.from("shared_content")
36
- .select("*, scc:shared_content_completed(id)")
39
+ let query = this.supabase.from("shared_content")
40
+ .select("*, scc:shared_content_completed(id, state)")
37
41
  .eq('content_type', contentType)
38
- .is('scc.id', null)
39
- .is('deleted_at', null)
40
- .limit(10);
42
+ .not('scc.state', 'in', '("completed","ongoing","hidden")')
43
+ .is('deleted_at', null);
44
+
45
+ if (options?.excludeIds && options.excludeIds.length > 0) {
46
+ const excludeIds = options.excludeIds.filter((id) => !id.startsWith('internal-temp-id-'));
47
+ // Supabase expects raw PostgREST syntax like '("id1","id2")'.
48
+ const excludeList = `(${excludeIds.map((id) => `"${id}"`).join(',')})`;
49
+ query = query.not('id', 'in', excludeList);
50
+ }
41
51
 
42
52
  if (filter) {
43
53
  query.contains('data', filter);
44
54
  }
45
55
 
46
- const { data: newAssignments, error } = await query;
56
+ const { data: newAssignments, error } = await query.limit(30);
47
57
 
48
58
  if (error) {
49
59
  console.error('error fetching new assignments:', error);
50
60
  throw new Error('error fetching new assignments');
51
61
  }
52
62
 
53
- console.log('newAssignments:', newAssignments);
63
+ // console.log('newAssignments:', newAssignments);
54
64
 
55
- if (newAssignments.length > 0) {
65
+ if (!(options?.alwaysGenerateNew) && newAssignments.length > 0) {
56
66
  const index = Math.floor(Math.random() * newAssignments.length);
57
67
  return newAssignments[index];
58
68
  }
59
69
 
60
- // generate new assignments
61
- const fullInstructions = await this.getGeneratorInstructions(contentType, generatorInstructions, filter);
62
-
63
- console.log('fullInstructions:', fullInstructions);
64
-
65
- const instructions = await this.rimoriClient.llm.getObject(fullInstructions);
70
+ const instructions = await this.generateNewAssignment(contentType, generatorInstructions, filter);
66
71
 
67
72
  console.log('instructions:', instructions);
68
73
 
69
- const { data: newAssignment, error: insertError } = await this.supabase.from("shared_content").insert({
70
- private: privateTopic,
71
- content_type: contentType,
72
- topic: instructions.topic,
74
+ //create the shared content object
75
+ const data: SharedContent<T> = {
76
+ id: "internal-temp-id-" + Math.random().toString(36).substring(2, 15),
77
+ contentType,
78
+ title: instructions.title,
73
79
  keywords: instructions.keywords.map(({ text }: { text: string }) => text),
74
- data: { ...instructions, topic: undefined, keywords: undefined, ...generatorInstructions.fixedProperties },
75
- }).select();
80
+ data: { ...instructions, title: undefined, keywords: undefined, ...generatorInstructions.fixedProperties },
81
+ privateTopic: options?.privateTopic,
82
+ }
76
83
 
77
- if (insertError) {
78
- console.error('error inserting new assignment:', insertError);
79
- throw new Error('error inserting new assignment');
84
+ if (options?.skipDbSave) {
85
+ return data;
80
86
  }
81
87
 
82
- return newAssignment[0];
88
+ return await this.createSharedContent(data);
89
+ }
90
+
91
+ private async generateNewAssignment(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter): Promise<any> {
92
+ const fullInstructions = await this.getGeneratorInstructions(contentType, generatorInstructions, filter);
93
+
94
+ console.log('fullInstructions:', fullInstructions);
95
+
96
+ return await this.rimoriClient.ai.getObject(fullInstructions);
83
97
  }
84
98
 
85
99
  private async getGeneratorInstructions(contentType: string, generatorInstructions: ObjectRequest, filter?: SharedContentFilter): Promise<ObjectRequest> {
@@ -88,7 +102,7 @@ export class SharedContentController {
88
102
  generatorInstructions.instructions += `
89
103
  The following topics are already taken: ${completedTopics.join(', ')}`;
90
104
 
91
- generatorInstructions.tool.topic = {
105
+ generatorInstructions.tool.title = {
92
106
  type: "string",
93
107
  description: "What the topic is about. Short. ",
94
108
  }
@@ -129,7 +143,55 @@ export class SharedContentController {
129
143
  }
130
144
 
131
145
  public async completeSharedContent(contentType: string, assignmentId: string) {
132
- await this.supabase.from("shared_content_completed").insert({ content_type: contentType, id: assignmentId });
146
+ // Idempotent completion: upsert on (id, user_id) so repeated calls don't fail
147
+ const { error } = await this.supabase
148
+ .from("shared_content_completed")
149
+ .upsert({ content_type: contentType, id: assignmentId } as any, { onConflict: 'id' });
150
+
151
+ if (error) {
152
+ console.error('error completing shared content:', error);
153
+ throw new Error('error completing shared content');
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Update state details for a shared content entry in shared_content_completed.
159
+ * Assumes table has columns: state ('completed'|'ongoing'|'hidden'), reaction ('liked'|'disliked'|null), bookmarked boolean.
160
+ * Upserts per (id, content_type, user).
161
+ * @param param
162
+ * @param param.contentType - The content type.
163
+ * @param param.id - The shared content id.
164
+ * @param param.state - The state to set.
165
+ * @param param.reaction - Optional reaction.
166
+ * @param param.bookmarked - Optional bookmark flag.
167
+ */
168
+ public async updateSharedContentState({
169
+ contentType,
170
+ id,
171
+ state,
172
+ reaction,
173
+ bookmarked,
174
+ }: {
175
+ contentType: string
176
+ id: string
177
+ state?: 'completed' | 'ongoing' | 'hidden'
178
+ reaction?: 'liked' | 'disliked' | null
179
+ bookmarked?: boolean
180
+ }): Promise<void> {
181
+ const payload: Record<string, unknown> = { content_type: contentType, id };
182
+ if (state !== undefined) payload.state = state;
183
+ if (reaction !== undefined) payload.reaction = reaction;
184
+ if (bookmarked !== undefined) payload.bookmarked = bookmarked;
185
+
186
+ // Prefer upsert, fall back to insert/update if upsert not allowed
187
+ const { error } = await this.supabase
188
+ .from('shared_content_completed')
189
+ .upsert(payload as any, { onConflict: 'id' });
190
+
191
+ if (error) {
192
+ console.error('error updating shared content state:', error);
193
+ throw new Error('error updating shared content state');
194
+ }
133
195
  }
134
196
 
135
197
  /**
@@ -0,0 +1,31 @@
1
+ export async function getSTTResponse(backendUrl: string, audio: Blob, token: string) {
2
+ const formData = new FormData();
3
+ formData.append('file', audio);
4
+
5
+ return await fetch(`${backendUrl}/voice/stt`, {
6
+ method: 'POST',
7
+ headers: { 'Authorization': `Bearer ${token}` },
8
+ body: formData,
9
+ }).then(r => r.json()).then(r => {
10
+ // console.log("STT response: ", r);
11
+ return r.text;
12
+ });
13
+ }
14
+
15
+ export async function getTTSResponse(backendUrl: string, request: TTSRequest, token: string) {
16
+ return await fetch(`${backendUrl}/voice/tts`, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'Authorization': `Bearer ${token}`
21
+ },
22
+ body: JSON.stringify(request),
23
+ }).then(r => r.blob());
24
+ }
25
+
26
+ interface TTSRequest {
27
+ input: string;
28
+ voice: string;
29
+ speed: number;
30
+ language?: string;
31
+ }
@@ -0,0 +1,16 @@
1
+ // Core functionality exports
2
+ export * from "../fromRimori/PluginTypes";
3
+ export * from "../plugin/PluginController";
4
+ export * from "../plugin/RimoriClient";
5
+ export * from "../utils/difficultyConverter";
6
+ export * from "../utils/Language";
7
+ export * from "../utils/PluginUtils";
8
+ export * from "../worker/WorkerSetup";
9
+ export { EventBusMessage } from "../fromRimori/EventBus";
10
+ export { Buddy, UserInfo } from "./controller/SettingsController";
11
+ export { SharedContent } from "./controller/SharedContentController";
12
+ export { Message, OnLLMResponse, ToolInvocation } from "./controller/AIController";
13
+ export { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from "../plugin/AccomplishmentHandler";
14
+ export { Tool } from "../fromRimori/PluginTypes";
15
+ export { SharedContentObjectRequest } from "./controller/SharedContentController";
16
+
@@ -39,7 +39,7 @@ 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: boolean = true;
42
+ private debugEnabled: boolean = false;
43
43
  private evName: string = "";
44
44
 
45
45
  private constructor() {
@@ -130,7 +130,7 @@ export class EventBusHandler {
130
130
  * @param topics - The topic of the event.
131
131
  * @param handler - The handler to be called when the event is emitted.
132
132
  * @param ignoreSender - The senders to ignore.
133
- * @returns The ids of the listeners.
133
+ * @returns An EventListener object containing an off() method to unsubscribe the listeners.
134
134
  */
135
135
  public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): EventListener {
136
136
  const ids = this.toArray(topics).map(topic => {
@@ -163,17 +163,35 @@ export class EventBusHandler {
163
163
  * @param sender - The sender of the event.
164
164
  * @param topic - The topic of the event.
165
165
  * @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
- * @returns The ids of the listeners.
166
+ * @returns An EventListener object containing an off() method to unsubscribe the listeners.
167
167
  */
168
- public respond(sender: string, topic: string, handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
169
- const listener = this.on(topic, async (data: EventBusMessage) => {
170
- const response = typeof handler === "function" ? await handler(data) : handler;
171
- this.emit(sender, topic, response, data.eventId);
172
- }, [sender]);
173
-
174
- this.logIfDebug(`Added respond listener ` + sender + " to topic " + topic, { listener, sender });
168
+ public respond(sender: string, topic: string | string[], handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
169
+ const topics = Array.isArray(topic) ? topic : [topic];
170
+ const listeners = topics.map(topic => {
171
+ const blackListedEventIds: number[] = [];
172
+ //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("self.") ? [sender] : [];
174
+
175
+ const listener = this.on(topic, async (data: EventBusMessage) => {
176
+ if (blackListedEventIds.includes(data.eventId)) {
177
+ // console.log("BLACKLISTED EVENT ID", data.eventId);
178
+ return;
179
+ }
180
+ blackListedEventIds.push(data.eventId);
181
+ if (blackListedEventIds.length > 20) {
182
+ blackListedEventIds.shift();
183
+ }
184
+ const response = typeof handler === "function" ? await handler(data) : handler;
185
+ this.emit(sender, topic, response, data.eventId);
186
+ }, finalIgnoreSender);
187
+
188
+ this.logIfDebug(`Added respond listener ` + sender + " to topic " + topic, { listener, sender });
189
+ return {
190
+ off: () => listener.off()
191
+ };
192
+ });
175
193
  return {
176
- off: () => listener.off()
194
+ off: () => listeners.forEach(listener => listener.off())
177
195
  };
178
196
  }
179
197
 
@@ -0,0 +1,205 @@
1
+ // whole configuration of a plugin (from the database)
2
+ export type Plugin<T extends {} = {}> = Omit<RimoriPluginConfig<T>, 'context_menu_actions'> & {
3
+ version: string;
4
+ endpoint: string;
5
+ assetEndpoint: string;
6
+ context_menu_actions: MenuEntry[];
7
+ release_channel: "alpha" | "beta" | "stable";
8
+ }
9
+
10
+ export type ActivePlugin = Plugin<{ active?: boolean }>
11
+
12
+ // browsable page of a plugin
13
+ export interface PluginPage {
14
+ id: string;
15
+ name: string;
16
+ url: string;
17
+ // Whether the page should be shown in the navbar
18
+ show: boolean;
19
+ description: string;
20
+ root: "vocabulary" | "grammar" | "reading" | "listening" | "watching" | "writing" | "speaking" | "other" | "community";
21
+ // The actions that can be triggered in the plugin
22
+ // The key is the action key. The other entries are additional properties needed when triggering the action
23
+ action?: {
24
+ key: string;
25
+ parameters: ObjectTool;
26
+ }
27
+ }
28
+
29
+ // a sidebar page of a plugin
30
+ export interface SidebarPage {
31
+ // identifier of the page. Used to know which page to trigger when clicking on the sidebar
32
+ id: string;
33
+ // name of the page. Shown in the settings
34
+ name: string;
35
+ // description of the page. Shown in the settings
36
+ description: string;
37
+ // relative or absolute URL or path to the plugin's page
38
+ url: string;
39
+ // relative or absolute URL or path to the plugin's icon image
40
+ icon: string;
41
+ }
42
+
43
+ // context menu entry being configured in the plugin configuration
44
+ export interface MenuEntry {
45
+ // id of the plugin that the menu entry belongs to
46
+ plugin_id: string;
47
+ // identifier of the menu entry action. Used to know which entry to trigger when clicking on the context menu
48
+ action_key: string;
49
+ // text of the menu entry. Shown in the context menu
50
+ text: string;
51
+ // icon of the menu entry. Shown in the context menu
52
+ icon?: React.ReactNode;
53
+ }
54
+
55
+ // an action from the main panel that can be triggered and performs an action in the main panel
56
+ export type MainPanelAction = {
57
+ plugin_id: string;
58
+ action_key: string;
59
+ } & Record<string, string>;
60
+
61
+ // an action from the context menu that can be triggered and performs an action in the sidebar plugin
62
+ export interface ContextMenuAction {
63
+ // selected text when clicking on the context menu
64
+ text: string;
65
+ // id of the plugin that the action belongs to
66
+ plugin_id: string;
67
+ // key of the action. Used to know which action to trigger when clicking on the context menu
68
+ action_key: string
69
+ }
70
+
71
+ /**
72
+ * Rimori plugin structure representing the complete configuration
73
+ * of a Rimori plugin with all metadata and configuration options.
74
+ */
75
+ export interface RimoriPluginConfig<T extends {} = {}> {
76
+ id: string;
77
+ /**
78
+ * Basic information about the plugin including branding and core details.
79
+ */
80
+ info: {
81
+ /** The display name of the plugin shown to users */
82
+ title: string;
83
+ /** Detailed description introducing the plugin */
84
+ description: string;
85
+ /** relative or absolute URL or path to the plugin's logo/icon image */
86
+ logo: string;
87
+ /** Optional website URL for the plugin's homepage or link to plugins owner for contributions */
88
+ website?: string;
89
+ }
90
+ /**
91
+ * Configuration for different types of pages.
92
+ */
93
+ pages: {
94
+ /** Optional external URL where the plugin is hosted instead of the default CDN */
95
+ external_hosted_url?: string;
96
+ /** Array of main plugin pages that appear in the application's main navigation (can be disabled using the 'show' flag) */
97
+ main: (PluginPage & T)[];
98
+ /** Array of sidebar pages that appear in the sidebar for quick access (can be disabled using the 'show' flag) */
99
+ sidebar: (SidebarPage & T)[];
100
+ /** Optional path to the plugin's settings/configuration page */
101
+ settings?: string;
102
+ /** Optional array of event topics the plugin pages can listen to for cross-plugin communication */
103
+ topics?: string[];
104
+ }
105
+ /**
106
+ * Context menu actions that the plugin registers to appear in right-click menus throughout the application.
107
+ */
108
+ context_menu_actions: Omit<MenuEntry, "plugin_id">[];
109
+ /**
110
+ * Documentation paths for different types of plugin documentation.
111
+ */
112
+ documentation: {
113
+ /** Path to the general overview documentation. It's shown upon installation of the plugin. */
114
+ overview_path: string;
115
+ /** Path to user-facing documentation and guides */
116
+ user_path: string;
117
+ /** Path to developer documentation for plugin development */
118
+ developer_path: string;
119
+ }
120
+ /**
121
+ * Configuration for the plugin's web worker if it uses background processing or exposes actions to other plugins.
122
+ */
123
+ worker?: {
124
+ /** Relative path to the web worker JavaScript file. Mostly it's 'web-worker.js' which is located in the public folder. */
125
+ url: string;
126
+ /** Optional array of event topics the worker should listen to in addition to events having the pluginId in the topic. Can be a wildcard. Example: 'global.topic.*' or 'pluginId.*' */
127
+ topics?: string[];
128
+ };
129
+ }
130
+
131
+ // copied from llm edge function
132
+
133
+ export interface Tool {
134
+ name: string;
135
+ description: string;
136
+ parameters: {
137
+ name: string;
138
+ description: string;
139
+ type: "string" | "number" | "boolean";
140
+ }[];
141
+ execute?: (args: Record<string, any>) => Promise<unknown> | unknown | void;
142
+ }
143
+
144
+ /**
145
+ * The tool definition structure is used for LLM function calling and plugin action parameters.
146
+ * It defines the schema for tools that can be used by Language Learning Models (LLMs)
147
+ * and plugin actions.
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * const flashcardTool: Tool = {
152
+ * total_amount: {
153
+ * type: 'string',
154
+ * enum: ['default', '10', '20', '50'],
155
+ * description: 'Number of flashcards to practice'
156
+ * },
157
+ * deck: {
158
+ * type: 'string',
159
+ * enum: ['latest', 'random', 'oldest', 'mix', 'best_known'],
160
+ * description: 'Type of deck to practice'
161
+ * }
162
+ * };
163
+ * ```
164
+ *
165
+ */
166
+ export type ObjectTool = {
167
+ [key: string]: ToolParameter;
168
+ };
169
+
170
+ /**
171
+ * Parameter definition for LLM tools and plugin actions.
172
+ * Defines the structure, validation rules, and metadata for individual tool parameters.
173
+ * Used to create type-safe interfaces between LLMs, plugins, and the Rimori platform.
174
+ */
175
+ interface ToolParameter {
176
+ /** The data type of the parameter - can be primitive, nested object, or array */
177
+ type: ToolParameterType;
178
+ /** Human-readable description of the parameter's purpose and usage */
179
+ description: string;
180
+ /** Optional array of allowed values for enumerated parameters */
181
+ enum?: string[];
182
+ /** Whether the parameter is optional */
183
+ optional?: boolean;
184
+ }
185
+
186
+ /**
187
+ * Union type defining all possible parameter types for LLM tools.
188
+ * Supports primitive types, nested objects for complex data structures,
189
+ * and arrays of objects for collections. The tuple notation [{}] indicates
190
+ * arrays of objects with a specific structure.
191
+ *
192
+ * @example Primitive: 'string' | 'number' | 'boolean'
193
+ * @example Nested object: { name: { type: 'string' }, age: { type: 'number' } }
194
+ * @example Array of objects: [{ id: { type: 'string' }, value: { type: 'number' } }]
195
+ */
196
+ type ToolParameterType =
197
+ | PrimitiveType
198
+ | { [key: string]: ToolParameter } // for nested objects
199
+ | [{ [key: string]: ToolParameter }]; // for arrays of objects (notice the tuple type)
200
+
201
+ /**
202
+ * Primitive data types supported by the LLM tool system.
203
+ * These align with JSON schema primitive types and TypeScript basic types.
204
+ */
205
+ type PrimitiveType = 'string' | 'number' | 'boolean';
@@ -1,14 +1,17 @@
1
1
  import React from "react";
2
- import { Message, Tool, ToolInvocation } from "../controller/AIController";
3
- import { usePlugin } from "../providers/PluginProvider";
2
+ import { Tool } from "../fromRimori/PluginTypes";
3
+ import { useRimori } from "../providers/PluginProvider";
4
+ import { Message, ToolInvocation } from "../core/controller/AIController";
4
5
 
5
6
  export function useChat(tools?: Tool[]) {
6
7
  const [messages, setMessages] = React.useState<Message[]>([]);
7
8
  const [isLoading, setIsLoading] = React.useState(false);
8
- const { llm } = usePlugin();
9
+ const { ai } = useRimori();
9
10
 
10
11
  const append = (appendMessages: Message[]) => {
11
- llm.getSteamedText([...messages, ...appendMessages], (id, message, finished: boolean, toolInvocations?: ToolInvocation[]) => {
12
+ const allMessages = [...messages, ...appendMessages];
13
+ setMessages(allMessages);
14
+ ai.getSteamedText(allMessages, (id, message, finished: boolean, toolInvocations?: ToolInvocation[]) => {
12
15
  const lastMessage = messages[messages.length - 1];
13
16
  setIsLoading(!finished);
14
17
 
@@ -16,7 +19,7 @@ export function useChat(tools?: Tool[]) {
16
19
  lastMessage.content = message;
17
20
  setMessages([...messages, lastMessage]);
18
21
  } else {
19
- setMessages([...messages, ...appendMessages, { id, role: 'assistant', content: message, toolInvocations }]);
22
+ setMessages([...allMessages, { id, role: 'assistant', content: message, toolCalls: toolInvocations }]);
20
23
  }
21
24
  }, tools);
22
25
  };
package/src/index.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  // Re-export everything
2
- export * from './core';
3
2
  export * from './components';
4
3
  export * from "./hooks/UseChatHook";
5
- export * from "./plugin/RimoriClient";
4
+ export * from "./plugin/PluginController";
6
5
  export * from "./providers/PluginProvider";
6
+ export * from "./cli/types/DatabaseTypes";
7
7
  export * from "./utils/difficultyConverter";
8
8
  export * from "./utils/PluginUtils";
9
- export * from "./plugin/PluginController";
9
+ export * from "./utils/Language";
10
+ export * from "./fromRimori/PluginTypes";
11
+ export { FirstMessages } from "./components/ai/utils";
12
+ export { AudioController } from "./plugin/AudioController";
@@ -1,4 +1,4 @@
1
- import { EventBus, EventBusMessage } from "./fromRimori/EventBus";
1
+ import { EventBus, EventBusMessage } from "../fromRimori/EventBus";
2
2
 
3
3
  export type AccomplishmentMessage = EventBusMessage<MicroAccomplishmentPayload>;
4
4
 
@@ -0,0 +1,58 @@
1
+ import { EventBus } from "../fromRimori/EventBus";
2
+
3
+ /**
4
+ * AudioController is a class that provides methods to record audio. It is a wrapper around the Capacitor Voice Recorder plugin. For more information, see https://github.com/tchvu3/capacitor-voice-recorder.
5
+ *
6
+ * @example
7
+ * const audioController = new AudioController();
8
+ * await audioController.startRecording();
9
+ */
10
+ export class AudioController {
11
+ private pluginId: string;
12
+
13
+ constructor(pluginId: string) {
14
+ this.pluginId = pluginId;
15
+ }
16
+
17
+ /**
18
+ * Start the recording.
19
+ *
20
+ * @example
21
+ * const audioController = new AudioController();
22
+ * await audioController.startRecording();
23
+ * @returns void
24
+ */
25
+ public async startRecording(): Promise<void> {
26
+ EventBus.emit(this.pluginId, "global.microphone.triggerStartRecording");
27
+ }
28
+
29
+ /**
30
+ * Stop the recording and return the audio data.
31
+ * @returns The audio data.
32
+ *
33
+ * @example
34
+ * const audioRef = new Audio(`data:${mimeType};base64,${base64Sound}`)
35
+ * audioRef.oncanplaythrough = () => audioRef.play()
36
+ * audioRef.load()
37
+ */
38
+ public async stopRecording(): Promise<{ recording: Blob, msDuration: number, mimeType: string }> {
39
+ const result = await EventBus.request<{ recording: Blob, msDuration: number, mimeType: string }>(this.pluginId, "global.microphone.triggerStopRecording");
40
+
41
+ return result.data;
42
+ }
43
+
44
+ public async pauseRecording(): Promise<boolean> {
45
+ const result = await EventBus.request<boolean>(this.pluginId, "global.microphone.triggerPauseRecording");
46
+ return result.data;
47
+ }
48
+
49
+ public async resumeRecording(): Promise<boolean> {
50
+ const result = await EventBus.request<boolean>(this.pluginId, "global.microphone.triggerResumeRecording");
51
+ return result.data;
52
+ }
53
+
54
+ public async getCurrentStatus(): Promise<"RECORDING" | "PAUSED" | "NONE"> {
55
+ const result = await EventBus.request<"RECORDING" | "PAUSED" | "NONE">(this.pluginId, "global.microphone.triggerGetCurrentStatus");
56
+ return result.data;
57
+ }
58
+ }