@rimori/client 1.0.4 → 1.1.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 (95) hide show
  1. package/README.md +13 -0
  2. package/dist/components/MarkdownEditor.js +6 -4
  3. package/dist/components/PluginController.d.ts +21 -0
  4. package/dist/components/PluginController.js +116 -0
  5. package/dist/components/ai/Assistant.js +1 -1
  6. package/dist/components/ai/Avatar.d.ts +5 -3
  7. package/dist/components/ai/Avatar.js +14 -6
  8. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +2 -1
  9. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +35 -14
  10. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -0
  11. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +3 -0
  12. package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +2 -0
  13. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +5 -0
  14. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +3 -0
  15. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -5
  16. package/dist/components/ai/utils.d.ts +1 -1
  17. package/dist/components.d.ts +1 -0
  18. package/dist/components.js +1 -0
  19. package/dist/controller/AIController.js +2 -1
  20. package/dist/controller/SettingsController.d.ts +18 -10
  21. package/dist/controller/SettingsController.js +28 -31
  22. package/dist/controller/SharedContentController.d.ts +58 -11
  23. package/dist/controller/SharedContentController.js +161 -26
  24. package/dist/controller/SidePluginController.d.ts +1 -12
  25. package/dist/core/components/ContextMenu.d.ts +10 -0
  26. package/dist/core/components/ContextMenu.js +93 -0
  27. package/dist/core.d.ts +2 -0
  28. package/dist/core.js +2 -0
  29. package/dist/hooks/UseChatHook.d.ts +1 -1
  30. package/dist/plugin/AccomplishmentHandler.d.ts +38 -0
  31. package/dist/plugin/AccomplishmentHandler.js +108 -0
  32. package/dist/plugin/ContextMenu.d.ts +17 -0
  33. package/dist/plugin/ContextMenu.js +45 -0
  34. package/dist/plugin/PluginController.js +9 -4
  35. package/dist/plugin/RimoriClient.d.ts +92 -65
  36. package/dist/plugin/RimoriClient.js +105 -75
  37. package/dist/plugin/ThemeSetter.js +4 -4
  38. package/dist/plugin/fromRimori/EventBus.d.ts +6 -3
  39. package/dist/plugin/fromRimori/EventBus.js +15 -9
  40. package/dist/plugin/fromRimori/PluginTypes.d.ts +51 -0
  41. package/dist/plugin/fromRimori/PluginTypes.js +1 -0
  42. package/dist/providers/PluginController.d.ts +21 -0
  43. package/dist/providers/PluginController.js +116 -0
  44. package/dist/providers/PluginProvider.js +26 -73
  45. package/dist/types/Actions.d.ts +4 -0
  46. package/dist/types/Actions.js +1 -0
  47. package/dist/utils/Language.d.ts +66 -0
  48. package/dist/utils/Language.js +67 -0
  49. package/dist/utils/difficultyConverter.d.ts +1 -0
  50. package/dist/utils/difficultyConverter.js +3 -0
  51. package/dist/worker/WorkerSetup.js +5 -4
  52. package/package.json +3 -3
  53. package/src/components/MarkdownEditor.tsx +78 -76
  54. package/src/components/ai/Assistant.tsx +1 -1
  55. package/src/components/ai/Avatar.tsx +65 -48
  56. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +81 -58
  57. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +4 -0
  58. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +6 -0
  59. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +51 -8
  60. package/src/components/ai/utils.ts +1 -1
  61. package/src/components.ts +2 -1
  62. package/src/controller/AIController.ts +2 -1
  63. package/src/controller/SettingsController.ts +80 -75
  64. package/src/controller/SharedContentController.ts +214 -53
  65. package/src/controller/SidePluginController.ts +1 -13
  66. package/src/core/components/ContextMenu.tsx +123 -0
  67. package/src/core.ts +3 -1
  68. package/src/hooks/UseChatHook.ts +17 -17
  69. package/src/plugin/AccomplishmentHandler.ts +165 -0
  70. package/src/plugin/PluginController.ts +107 -100
  71. package/src/plugin/RimoriClient.ts +267 -250
  72. package/src/plugin/ThemeSetter.ts +4 -5
  73. package/src/plugin/fromRimori/EventBus.ts +23 -12
  74. package/src/plugin/fromRimori/PluginTypes.ts +67 -0
  75. package/src/providers/PluginProvider.tsx +63 -110
  76. package/src/types/Actions.ts +6 -0
  77. package/src/utils/Language.ts +70 -0
  78. package/src/utils/difficultyConverter.ts +4 -0
  79. package/src/worker/WorkerSetup.ts +5 -4
  80. package/dist/components/avatar/Assistant.d.ts +0 -9
  81. package/dist/components/avatar/Assistant.js +0 -59
  82. package/dist/components/avatar/Avatar.d.ts +0 -12
  83. package/dist/components/avatar/Avatar.js +0 -42
  84. package/dist/components/avatar/EmbeddedAssistent/AudioInputField.d.ts +0 -7
  85. package/dist/components/avatar/EmbeddedAssistent/AudioInputField.js +0 -38
  86. package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -7
  87. package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.js +0 -59
  88. package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
  89. package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.js +0 -84
  90. package/dist/components/avatar/EmbeddedAssistent/TTS/Player.d.ts +0 -25
  91. package/dist/components/avatar/EmbeddedAssistent/TTS/Player.js +0 -180
  92. package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.d.ts +0 -7
  93. package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.js +0 -45
  94. package/dist/components/avatar/utils.d.ts +0 -6
  95. package/dist/components/avatar/utils.js +0 -14
@@ -31,11 +31,15 @@ interface Listeners<T = EventPayload> {
31
31
  ignoreSender?: string[];
32
32
  }
33
33
 
34
+ export interface EventListener {
35
+ off: () => void;
36
+ }
37
+
34
38
  export class EventBusHandler {
35
39
  private listeners: Map<string, Set<Listeners<EventPayload>>> = new Map();
36
40
  private responseResolvers: Map<number, (value: EventBusMessage<unknown>) => void> = new Map();
37
41
  private static instance: EventBusHandler | null = null;
38
- private debugEnabled: boolean = false;
42
+ private debugEnabled: boolean = true;
39
43
  private evName: string = "";
40
44
 
41
45
  private constructor() {
@@ -128,8 +132,9 @@ export class EventBusHandler {
128
132
  * @param ignoreSender - The senders to ignore.
129
133
  * @returns The ids of the listeners.
130
134
  */
131
- public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): string[] {
132
- return this.toArray(topics).map(topic => {
135
+ public on<T = EventPayload>(topics: string | string[], handler: EventHandler<T>, ignoreSender: string[] = []): EventListener {
136
+ const ids = this.toArray(topics).map(topic => {
137
+ this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
133
138
  if (!this.validateTopic(topic)) {
134
139
  this.logAndThrowError(true, `Invalid topic: ` + topic);
135
140
  }
@@ -147,6 +152,10 @@ export class EventBusHandler {
147
152
 
148
153
  return btoa(JSON.stringify({ topic, id }));
149
154
  });
155
+
156
+ return {
157
+ off: () => this.off(ids)
158
+ };
150
159
  }
151
160
 
152
161
  /**
@@ -156,14 +165,16 @@ export class EventBusHandler {
156
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.
157
166
  * @returns The ids of the listeners.
158
167
  */
159
- public respond(sender: string, topic: string, handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): string[] {
160
- const ids = this.on(topic, async (data: EventBusMessage) => {
168
+ public respond(sender: string, topic: string, handler: EventPayload | ((data: EventBusMessage) => EventPayload | Promise<EventPayload>)): EventListener {
169
+ const listener = this.on(topic, async (data: EventBusMessage) => {
161
170
  const response = typeof handler === "function" ? await handler(data) : handler;
162
171
  this.emit(sender, topic, response, data.eventId);
163
172
  }, [sender]);
164
173
 
165
- this.logIfDebug(`Added respond listener ` + sender + " to topic " + topic, { listenerIds: ids, sender });
166
- return ids;
174
+ this.logIfDebug(`Added respond listener ` + sender + " to topic " + topic, { listener, sender });
175
+ return {
176
+ off: () => listener.off()
177
+ };
167
178
  }
168
179
 
169
180
  /**
@@ -177,21 +188,21 @@ export class EventBusHandler {
177
188
  return;
178
189
  }
179
190
 
180
- let ids: string[] = [];
191
+ let listener: EventListener | undefined;
181
192
  const wrapper = (event: EventBusMessage<T>) => {
182
193
  handler(event);
183
- this.off(ids);
194
+ listener?.off();
184
195
  };
185
- ids = this.on(topic, wrapper);
196
+ listener = this.on(topic, wrapper);
186
197
 
187
- this.logIfDebug(`Added once listener ` + topic, { listenerIds: ids, topic });
198
+ this.logIfDebug(`Added once listener ` + topic, { listener, topic });
188
199
  }
189
200
 
190
201
  /**
191
202
  * Unsubscribes from an event on the event bus.
192
203
  * @param listenerIds - The ids of the listeners to unsubscribe from.
193
204
  */
194
- public off(listenerIds: string | string[]): void {
205
+ private off(listenerIds: string | string[]): void {
195
206
  this.toArray(listenerIds).forEach(fullId => {
196
207
  const { topic, id } = JSON.parse(atob(fullId));
197
208
 
@@ -0,0 +1,67 @@
1
+
2
+
3
+ // whole configuration of a plugin (from the database)
4
+ export interface Plugin {
5
+ id: string;
6
+ title: string;
7
+ description: string;
8
+ git_repository: string;
9
+ website: string;
10
+ icon_url: string;
11
+ version: string;
12
+ author: string;
13
+ endpoint: string;
14
+ context_menu_actions: MenuEntry[];
15
+ plugin_pages: PluginPage[];
16
+ sidebar_pages: SidebarPage[];
17
+ settings_page: string;
18
+ worker?: {
19
+ url: string;
20
+ topics?: string[];
21
+ };
22
+ }
23
+
24
+ // browsable page of a plugin
25
+ export interface PluginPage {
26
+ name: string;
27
+ url: string;
28
+ // Whether the page should be shown in the navbar
29
+ show: boolean;
30
+ description: string;
31
+ root: string;
32
+ // The actions that can be triggered in the plugin
33
+ // The key is the action key. The other entries are additional properties needed when triggering the action
34
+ action?: (Record<string, string> & {
35
+ key: string;
36
+ })
37
+ }
38
+
39
+ // a sidebar page of a plugin
40
+ export interface SidebarPage {
41
+ name: string;
42
+ url: string;
43
+ iconUrl: string;
44
+ description: string;
45
+ actionKey: string;
46
+ }
47
+
48
+ // context menu entry being configured in the plugin configuration
49
+ export interface MenuEntry {
50
+ text: string;
51
+ pluginId: string;
52
+ actionKey: string;
53
+ icon?: React.ReactNode;
54
+ }
55
+
56
+ // an action from the main panel that can be triggered and performs an action in the main panel
57
+ export type MainPanelAction = {
58
+ pluginId: string;
59
+ actionKey: string;
60
+ } & Record<string, string>;
61
+
62
+ // an action from the context menu that can be triggered and performs an action in the sidebar plugin
63
+ export interface ContextMenuAction {
64
+ text: string;
65
+ pluginId: string;
66
+ actionKey: string
67
+ }
@@ -2,123 +2,76 @@ import React, { createContext, useContext, ReactNode, useEffect, useState } from
2
2
  import { PluginController } from '../plugin/PluginController';
3
3
  import { RimoriClient } from '../plugin/RimoriClient';
4
4
  import { EventBusHandler } from '../plugin/fromRimori/EventBus';
5
+ import ContextMenu from '../core/components/ContextMenu';
5
6
 
6
7
  interface PluginProviderProps {
7
- children: ReactNode;
8
- pluginId: string;
8
+ children: ReactNode;
9
+ pluginId: string;
9
10
  }
10
11
 
11
- EventBusHandler.getInstance("Plugin EventBus");
12
-
13
12
  const PluginContext = createContext<RimoriClient | null>(null);
14
13
 
15
14
  export const PluginProvider: React.FC<PluginProviderProps> = ({ children, pluginId }) => {
16
- const [plugin, setPlugin] = useState<RimoriClient | null>(null);
17
- const [contextMenuOnSelect, setContextMenuOnTextSelection] = useState(false);
18
-
19
- //route change
20
- useEffect(() => {
21
- let lastHash = window.location.hash;
22
-
23
- setInterval(() => {
24
- if (lastHash !== window.location.hash) {
25
- lastHash = window.location.hash;
26
- console.log('url changed:', lastHash);
27
- plugin?.event.emit('session.triggerUrlChange', window.location.hash);
28
- }
29
- }, 100);
30
- PluginController.getInstance(pluginId).then(setPlugin);
31
- }, []);
32
-
33
- //check if context menu opens on text selection
34
- useEffect(() => {
35
- if (!plugin) return;
36
- plugin.plugin.getUserInfo().then((userInfo) => {
37
- setContextMenuOnTextSelection(userInfo.contextMenuOnSelect);
38
- }).catch(error => {
39
- console.error('Error fetching settings:', error);
40
- });
41
- }, [plugin]);
42
-
43
- //detect page height change
44
- useEffect(() => {
45
- const body = document.body;
46
- const handleResize = () => plugin?.event.emit('session.triggerHeightChange', body.clientHeight);
47
- body.addEventListener('resize', handleResize);
48
- handleResize();
49
- return () => body.removeEventListener('resize', handleResize);
50
- }, [plugin]);
51
-
52
- //context menu
53
- useEffect(() => {
54
- let lastMouseX = 0;
55
- let lastMouseY = 0;
56
- let isSelecting = false;
57
-
58
- // Track mouse position
59
- const handleMouseMove = (e: MouseEvent) => {
60
- lastMouseX = e.clientX;
61
- lastMouseY = e.clientY;
62
- };
63
-
64
- const handleContextMenu = (e: MouseEvent) => {
65
- const selection = window.getSelection()?.toString().trim();
66
- if (selection) {
67
- e.preventDefault();
68
- // console.log('context menu handled', selection);
69
- plugin?.event.emit('global.contextMenu.trigger', { text: selection, x: e.clientX, y: e.clientY, open: true });
70
- }
71
- };
72
-
73
- const handleSelectionChange = () => {
74
- // if (triggerOnTextSelection) {
75
- const selection = window.getSelection()?.toString().trim();
76
- const open = !!selection && isSelecting;
77
- // console.log('Selection change, contextMenuOnSelect:', contextMenuOnSelect);
78
- plugin?.event.emit('global.contextMenu.trigger', { text: selection, x: lastMouseX, y: lastMouseY, open });
79
- // }
80
- };
81
- const handleMouseUpDown = (e: MouseEvent) => {
82
- if (e.type === 'mousedown') {
83
- isSelecting = false;
84
- } else if (e.type === 'mouseup') {
85
- isSelecting = true;
86
- // console.log('mouseup, contextMenuOnSelect:', contextMenuOnSelect);
87
- if (contextMenuOnSelect) {
88
- handleSelectionChange();
89
- }
90
- }
91
- };
92
-
93
- document.addEventListener('contextmenu', handleContextMenu);
94
- document.addEventListener('selectionchange', handleSelectionChange);
95
- document.addEventListener("mousemove", handleMouseMove);
96
- document.addEventListener('mousedown', handleMouseUpDown);
97
- document.addEventListener('mouseup', handleMouseUpDown);
98
- return () => {
99
- document.removeEventListener("mousemove", handleMouseMove);
100
- document.removeEventListener('contextmenu', handleContextMenu);
101
- document.removeEventListener('selectionchange', handleSelectionChange);
102
- document.removeEventListener('mousedown', handleMouseUpDown);
103
- document.removeEventListener('mouseup', handleMouseUpDown);
104
- };
105
- }, [plugin, contextMenuOnSelect]);
106
-
107
- if (!plugin) {
108
- return ""
109
- }
110
-
111
- return (
112
- <PluginContext.Provider value={plugin}>
113
- {children}
114
- </PluginContext.Provider>
115
- );
15
+ const [plugin, setPlugin] = useState<RimoriClient | null>(null);
16
+ initEventBus(pluginId);
17
+
18
+ useEffect(() => {
19
+ PluginController.getInstance(pluginId).then(setPlugin);
20
+ }, [pluginId]);
21
+
22
+ //route change
23
+ useEffect(() => {
24
+ if (!plugin) return;
25
+
26
+ const url = new URL(window.location.href);
27
+ //sidebar pages should not report url changes
28
+ if (url.searchParams.get("applicationMode") === "sidebar") return;
29
+
30
+ let lastHash = url.hash;
31
+ const emitUrlChange = (url: string) => plugin.event.emit('session.triggerUrlChange', { url });
32
+
33
+ const interval = setInterval(() => {
34
+ if (lastHash === window.location.hash) return;
35
+ lastHash = window.location.hash;
36
+ // console.log('url changed:', lastHash);
37
+ emitUrlChange(lastHash);
38
+ }, 1000);
39
+
40
+ emitUrlChange(lastHash);
41
+ return () => clearInterval(interval);
42
+ }, [plugin]);
43
+
44
+ //detect page height change
45
+ useEffect(() => {
46
+ const body = document.body;
47
+ const handleResize = () => plugin?.event.emit('session.triggerHeightChange', body.clientHeight);
48
+ body.addEventListener('resize', handleResize);
49
+ handleResize();
50
+ return () => body.removeEventListener('resize', handleResize);
51
+ }, [plugin]);
52
+
53
+ if (!plugin) {
54
+ return ""
55
+ }
56
+
57
+ return (
58
+ <PluginContext.Provider value={plugin}>
59
+ <ContextMenu client={plugin} />
60
+ {children}
61
+ </PluginContext.Provider>
62
+ );
116
63
  };
117
64
 
118
65
  export const usePlugin = () => {
119
- const context = useContext(PluginContext);
120
- if (context === null) {
121
- throw new Error('usePlugin must be used within an PluginProvider');
122
- }
123
- return context;
124
- };
66
+ const context = useContext(PluginContext);
67
+ if (context === null) {
68
+ throw new Error('usePlugin must be used within an PluginProvider');
69
+ }
70
+ return context;
71
+ };
72
+
73
+ function initEventBus(pluginId: string) {
74
+ const url = new URL(window.location.href);
75
+ const isSidebar = url.searchParams.get("applicationMode") === "sidebar";
76
+ EventBusHandler.getInstance("Plugin EventBus " + pluginId + " " + (isSidebar ? "sidebar" : "main"));
77
+ }
@@ -0,0 +1,6 @@
1
+ // the received action when the plugin pages action is triggered
2
+ // This is based upon plugin_pages configuration of the plugin
3
+ export type MainPanelAction = {
4
+ pluginId: string;
5
+ actionKey: string;
6
+ } & Record<string, string>;
@@ -0,0 +1,70 @@
1
+ export const languageKeys = {
2
+ sq: "albanian",
3
+ ar: "arabic",
4
+ hy: "armenian",
5
+ az: "azerbaijani",
6
+ bn: "bengali",
7
+ bs: "bosnian",
8
+ bg: "bulgarian",
9
+ ca: "catalan",
10
+ zh: "chinese",
11
+ hr: "croatian",
12
+ cs: "czech",
13
+ da: "danish",
14
+ nl: "dutch",
15
+ en: "english",
16
+ et: "estonian",
17
+ fi: "finnish",
18
+ fr: "french",
19
+ gl: "galician",
20
+ de: "german",
21
+ el: "greek",
22
+ he: "hebrew",
23
+ hi: "hindi",
24
+ hu: "hungarian",
25
+ is: "icelandic",
26
+ id: "indonesian",
27
+ it: "italian",
28
+ ja: "japanese",
29
+ kn: "kannada",
30
+ kk: "kazakh",
31
+ ko: "korean",
32
+ lv: "latvian",
33
+ lt: "lithuanian",
34
+ mk: "macedonian",
35
+ ms: "malay",
36
+ mr: "marathi",
37
+ mi: "maori",
38
+ ne: "nepali",
39
+ no: "norwegian",
40
+ fa: "persian",
41
+ pl: "polish",
42
+ pt: "portuguese",
43
+ ro: "romanian",
44
+ ru: "russian",
45
+ sr: "serbian",
46
+ sk: "slovak",
47
+ sl: "slovenian",
48
+ es: "spanish",
49
+ sw: "swahili",
50
+ sv: "swedish",
51
+ tl: "filipino",
52
+ ta: "tamil",
53
+ th: "thai",
54
+ tr: "turkish",
55
+ uk: "ukrainian",
56
+ ur: "urdu",
57
+ vi: "vietnamese",
58
+ cy: "welsh"
59
+ } as const;
60
+
61
+ export type Language = keyof typeof languageKeys;
62
+
63
+ /**
64
+ * Get the language name from the language code
65
+ * @param languageCode The code of the language
66
+ * @returns The language name
67
+ */
68
+ export function getLanguageName(languageCode: Language): string {
69
+ return languageKeys[languageCode];
70
+ }
@@ -9,3 +9,7 @@ export function getDifficultyLevel(difficulty: LanguageLevel): number {
9
9
  export function getDifficultyLabel(difficulty: number): LanguageLevel {
10
10
  return codes[difficulty] as LanguageLevel;
11
11
  }
12
+
13
+ export function getNeighborDifficultyLevel(difficulty: LanguageLevel, difficultyAdjustment: number): LanguageLevel {
14
+ return getDifficultyLabel(getDifficultyLevel(difficulty) + difficultyAdjustment);
15
+ }
@@ -13,12 +13,13 @@ let debugEnabled = false;
13
13
  export function setupWorker(init: (controller: RimoriClient) => void | Promise<void>) {
14
14
  // Mock of the window object for the worker context to be able to use the PluginController.
15
15
  const mockWindow = {
16
+ isWorker: true,
16
17
  location: { search: '?secret=123' },
17
18
  parent: {
18
19
  postMessage: (message: { event: EventBusMessage }) => {
19
20
  message.event.sender = "worker." + message.event.sender;
20
21
  checkDebugMode(message.event);
21
- logIfDebug('[Worker] sending event to Rimori', message.event);
22
+ logIfDebug('sending event to Rimori', message.event);
22
23
  self.postMessage(message)
23
24
  }
24
25
  },
@@ -39,7 +40,7 @@ export function setupWorker(init: (controller: RimoriClient) => void | Promise<v
39
40
  // Handle init message from Rimori.
40
41
  self.onmessage = async (response: MessageEvent) => {
41
42
  checkDebugMode(response.data);
42
- logIfDebug('[Worker] message received', response.data);
43
+ logIfDebug('Message received', response.data);
43
44
 
44
45
  const event = response.data as EventBusMessage;
45
46
 
@@ -48,9 +49,9 @@ export function setupWorker(init: (controller: RimoriClient) => void | Promise<v
48
49
  mockWindow.APP_CONFIG.SUPABASE_URL = event.data.supabaseUrl;
49
50
  mockWindow.APP_CONFIG.SUPABASE_ANON_KEY = event.data.supabaseAnonKey;
50
51
  controller = await PluginController.getInstance(event.data.pluginId);
51
- logIfDebug('[Worker] Worker initialized.');
52
+ logIfDebug('Worker initialized.');
52
53
  await init(controller);
53
- logIfDebug('[Worker] Plugin listeners initialized.');
54
+ logIfDebug('Plugin listeners initialized.');
54
55
  }
55
56
  const initEvent: EventBusMessage = {
56
57
  timestamp: new Date().toISOString(),
@@ -1,9 +0,0 @@
1
- import { FirstMessages } from './utils';
2
- interface Props {
3
- voiceId: any;
4
- avatarImageUrl: string;
5
- onComplete: (result: any) => void;
6
- autoStartConversation?: FirstMessages;
7
- }
8
- export declare function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }: Props): import("react/jsx-runtime").JSX.Element;
9
- export {};
@@ -1,59 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useMemo } from 'react';
3
- import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
4
- import { AudioInputField } from './EmbeddedAssistent/AudioInputField';
5
- import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
6
- import Markdown from 'react-markdown';
7
- import { useChat } from '../../hooks/UseChatHook';
8
- import { usePlugin } from '../../components';
9
- import { getFirstMessages } from './utils';
10
- export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }) {
11
- var _a;
12
- const [oralCommunication, setOralCommunication] = React.useState(true);
13
- const { llm, event } = usePlugin();
14
- const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
15
- const { messages, append, isLoading, setMessages } = useChat();
16
- const lastAssistantMessage = (_a = [...messages].filter((m) => m.role === 'assistant').pop()) === null || _a === void 0 ? void 0 : _a.content;
17
- useEffect(() => {
18
- sender.setOnLoudnessChange((value) => event.emit('self.avatar.triggerLoudness', value));
19
- if (!autoStartConversation) {
20
- return;
21
- }
22
- setMessages(getFirstMessages(autoStartConversation));
23
- // append([{ role: 'user', content: autoStartConversation.userMessage }]);
24
- if (autoStartConversation.assistantMessage) {
25
- // console.log("autostartmessages", { autoStartConversation, isLoading });
26
- sender.handleNewText(autoStartConversation.assistantMessage, isLoading);
27
- }
28
- }, []);
29
- useEffect(() => {
30
- var _a;
31
- let message = lastAssistantMessage;
32
- if (message !== ((_a = messages[messages.length - 1]) === null || _a === void 0 ? void 0 : _a.content)) {
33
- message = undefined;
34
- }
35
- sender.handleNewText(message, isLoading);
36
- }, [messages, isLoading]);
37
- const lastMessage = messages[messages.length - 1];
38
- useEffect(() => {
39
- console.log("lastMessage", lastMessage);
40
- const toolInvocations = lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.toolInvocations;
41
- if (toolInvocations && toolInvocations.length > 0) {
42
- console.log("toolInvocations", toolInvocations);
43
- onComplete(toolInvocations[0].args);
44
- }
45
- }, [lastMessage]);
46
- if ((lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.toolInvocations) && lastMessage.toolInvocations.length > 0) {
47
- console.log("lastMessage test2", lastMessage);
48
- const args = lastMessage.toolInvocations[0].args;
49
- const success = args.explanationUnderstood === "TRUE" || args.studentKnowsTopic === "TRUE";
50
- return _jsxs("div", { className: "px-5 pt-5 overflow-y-auto text-center", style: { height: "478px" }, children: [_jsx("h1", { className: 'text-center mt-5 mb-5', children: success ? "Great job!" : "You failed" }), _jsx("p", { children: args.improvementHints })] });
51
- }
52
- return (_jsxs("div", { children: [oralCommunication && _jsx(CircleAudioAvatar, { imageUrl: avatarImageUrl, className: 'mx-auto my-10' }), _jsx("div", { className: "w-full", children: lastAssistantMessage && _jsx("div", { className: "px-5 pt-5 overflow-y-auto remirror-theme", style: { height: "4k78px" }, children: _jsx(Markdown, { children: lastAssistantMessage }) }) }), _jsx(AudioInputField, { blockSubmission: isLoading, onSubmit: message => {
53
- append([{ role: 'user', content: message, id: messages.length.toString() }]);
54
- }, onAudioControl: voice => {
55
- setOralCommunication(voice);
56
- sender.setVolume(voice ? 1 : 0);
57
- } })] }));
58
- }
59
- ;
@@ -1,12 +0,0 @@
1
- import { Tool } from '../../core';
2
- import { FirstMessages } from './utils';
3
- interface Props {
4
- title?: string;
5
- voiceId: any;
6
- avatarImageUrl: string;
7
- agentTools: Tool[];
8
- onComplete: (result: Record<string, string>) => void;
9
- autoStartConversation?: FirstMessages;
10
- }
11
- export declare function Avatar({ avatarImageUrl, voiceId, onComplete, title, agentTools, autoStartConversation }: Props): import("react/jsx-runtime").JSX.Element;
12
- export {};
@@ -1,42 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo } from 'react';
3
- import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecoder';
4
- import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
5
- import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
6
- import { useChat } from '../../hooks/UseChatHook';
7
- import { usePlugin } from '../../components';
8
- import { getFirstMessages } from './utils';
9
- export function Avatar({ avatarImageUrl, voiceId, onComplete, title, agentTools, autoStartConversation }) {
10
- var _a;
11
- const { llm, event } = usePlugin();
12
- const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
13
- const { messages, append, isLoading, lastMessage, setMessages } = useChat(agentTools);
14
- useEffect(() => {
15
- console.log("messages", messages);
16
- }, [messages]);
17
- useEffect(() => {
18
- sender.setOnLoudnessChange((value) => event.emit('self.avatar.triggerLoudness', value));
19
- if (!autoStartConversation)
20
- return;
21
- setMessages(getFirstMessages(autoStartConversation));
22
- // append([{ role: 'user', content: autoStartConversation.userMessage }]);
23
- if (autoStartConversation.assistantMessage) {
24
- // console.log("autostartmessages", { autoStartConversation, isLoading });
25
- sender.handleNewText(autoStartConversation.assistantMessage, isLoading);
26
- }
27
- }, []);
28
- useEffect(() => {
29
- if ((lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role) === 'assistant') {
30
- sender.handleNewText(lastMessage.content, isLoading);
31
- }
32
- }, [lastMessage, isLoading]);
33
- const invocation = (_a = lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.toolInvocations) === null || _a === void 0 ? void 0 : _a[0];
34
- useEffect(() => {
35
- if (invocation)
36
- onComplete(invocation.args);
37
- }, [lastMessage]);
38
- return (_jsxs("div", { className: 'pb-8', children: [title && _jsx("p", { className: "text-center mt-5 w-3/4 mx-auto rounded-lg dark:text-gray-100", children: title }), _jsx(CircleAudioAvatar, { imageUrl: avatarImageUrl, width: "250px", className: 'mx-auto' }), _jsx("div", { className: 'w-16 h-16 flex text-4xl shadow-lg flex-row justify-center items-center rounded-full mx-auto bg-gray-400 dark:bg-gray-800', children: _jsx(VoiceRecorder, { className: 'w-7', iconSize: '300', onVoiceRecorded: (message) => {
39
- append([{ role: 'user', content: "Message(" + Math.floor((messages.length + 1) / 2) + "): " + message, id: messages.length.toString() }]);
40
- } }) })] }));
41
- }
42
- ;
@@ -1,7 +0,0 @@
1
- interface AudioInputFieldProps {
2
- onSubmit: (text: string) => void;
3
- onAudioControl?: (voice: boolean) => void;
4
- blockSubmission?: boolean;
5
- }
6
- export declare function AudioInputField({ onSubmit, onAudioControl, blockSubmission }: AudioInputFieldProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
@@ -1,38 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { VoiceRecorder } from './VoiceRecoder';
4
- import { BiSolidRightArrow } from "react-icons/bi";
5
- import { HiMiniSpeakerXMark, HiMiniSpeakerWave } from "react-icons/hi2";
6
- export function AudioInputField({ onSubmit, onAudioControl, blockSubmission = false }) {
7
- const [text, setText] = useState('');
8
- const [audioEnabled, setAudioEnabled] = useState(true);
9
- const handleSubmit = (manualText) => {
10
- if (blockSubmission)
11
- return;
12
- const sendableText = manualText || text;
13
- if (sendableText.trim()) {
14
- onSubmit(sendableText);
15
- setTimeout(() => {
16
- setText('');
17
- }, 100);
18
- }
19
- };
20
- const handleKeyDown = (e) => {
21
- if (blockSubmission)
22
- return;
23
- if (e.key === 'Enter' && e.ctrlKey) {
24
- setText(text + '\n');
25
- }
26
- else if (e.key === 'Enter') {
27
- handleSubmit();
28
- }
29
- };
30
- return (_jsxs("div", { className: "flex items-center bg-gray-600 pt-2 pb-2 p-2", children: [onAudioControl && _jsx("button", { onClick: () => {
31
- onAudioControl(!audioEnabled);
32
- setAudioEnabled(!audioEnabled);
33
- }, className: "cursor-default", children: audioEnabled ? _jsx(HiMiniSpeakerWave, { className: 'w-9 h-9 cursor-pointer' }) : _jsx(HiMiniSpeakerXMark, { className: 'w-9 h-9 cursor-pointer' }) }), _jsx(VoiceRecorder, { onVoiceRecorded: (m) => {
34
- console.log('onVoiceRecorded', m);
35
- handleSubmit(m);
36
- } }), _jsx("textarea", { value: text, onChange: (e) => setText(e.target.value), onKeyDown: handleKeyDown, className: "flex-1 border-none rounded-lg p-2 text-gray-800 focus::outline-none", placeholder: 'Type a message...', disabled: blockSubmission }), _jsx("button", { onClick: () => handleSubmit(), className: "cursor-default", disabled: blockSubmission, children: _jsx(BiSolidRightArrow, { className: 'w-9 h-10 cursor-pointer' }) })] }));
37
- }
38
- ;
@@ -1,7 +0,0 @@
1
- interface CircleAudioAvatarProps {
2
- width?: string;
3
- imageUrl: string;
4
- className?: string;
5
- }
6
- export declare function CircleAudioAvatar({ imageUrl, className, width }: CircleAudioAvatarProps): import("react/jsx-runtime").JSX.Element;
7
- export {};