@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.
- package/README.md +13 -0
- package/dist/components/MarkdownEditor.js +6 -4
- package/dist/components/PluginController.d.ts +21 -0
- package/dist/components/PluginController.js +116 -0
- package/dist/components/ai/Assistant.js +1 -1
- package/dist/components/ai/Avatar.d.ts +5 -3
- package/dist/components/ai/Avatar.js +14 -6
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +2 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +35 -14
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +3 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +2 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +5 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +3 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -5
- package/dist/components/ai/utils.d.ts +1 -1
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/controller/AIController.js +2 -1
- package/dist/controller/SettingsController.d.ts +18 -10
- package/dist/controller/SettingsController.js +28 -31
- package/dist/controller/SharedContentController.d.ts +58 -11
- package/dist/controller/SharedContentController.js +161 -26
- package/dist/controller/SidePluginController.d.ts +1 -12
- package/dist/core/components/ContextMenu.d.ts +10 -0
- package/dist/core/components/ContextMenu.js +93 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +2 -0
- package/dist/hooks/UseChatHook.d.ts +1 -1
- package/dist/plugin/AccomplishmentHandler.d.ts +38 -0
- package/dist/plugin/AccomplishmentHandler.js +108 -0
- package/dist/plugin/ContextMenu.d.ts +17 -0
- package/dist/plugin/ContextMenu.js +45 -0
- package/dist/plugin/PluginController.js +9 -4
- package/dist/plugin/RimoriClient.d.ts +92 -65
- package/dist/plugin/RimoriClient.js +105 -75
- package/dist/plugin/ThemeSetter.js +4 -4
- package/dist/plugin/fromRimori/EventBus.d.ts +6 -3
- package/dist/plugin/fromRimori/EventBus.js +15 -9
- package/dist/plugin/fromRimori/PluginTypes.d.ts +51 -0
- package/dist/plugin/fromRimori/PluginTypes.js +1 -0
- package/dist/providers/PluginController.d.ts +21 -0
- package/dist/providers/PluginController.js +116 -0
- package/dist/providers/PluginProvider.js +26 -73
- package/dist/types/Actions.d.ts +4 -0
- package/dist/types/Actions.js +1 -0
- package/dist/utils/Language.d.ts +66 -0
- package/dist/utils/Language.js +67 -0
- package/dist/utils/difficultyConverter.d.ts +1 -0
- package/dist/utils/difficultyConverter.js +3 -0
- package/dist/worker/WorkerSetup.js +5 -4
- package/package.json +3 -3
- package/src/components/MarkdownEditor.tsx +78 -76
- package/src/components/ai/Assistant.tsx +1 -1
- package/src/components/ai/Avatar.tsx +65 -48
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +81 -58
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +4 -0
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +6 -0
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +51 -8
- package/src/components/ai/utils.ts +1 -1
- package/src/components.ts +2 -1
- package/src/controller/AIController.ts +2 -1
- package/src/controller/SettingsController.ts +80 -75
- package/src/controller/SharedContentController.ts +214 -53
- package/src/controller/SidePluginController.ts +1 -13
- package/src/core/components/ContextMenu.tsx +123 -0
- package/src/core.ts +3 -1
- package/src/hooks/UseChatHook.ts +17 -17
- package/src/plugin/AccomplishmentHandler.ts +165 -0
- package/src/plugin/PluginController.ts +107 -100
- package/src/plugin/RimoriClient.ts +267 -250
- package/src/plugin/ThemeSetter.ts +4 -5
- package/src/plugin/fromRimori/EventBus.ts +23 -12
- package/src/plugin/fromRimori/PluginTypes.ts +67 -0
- package/src/providers/PluginProvider.tsx +63 -110
- package/src/types/Actions.ts +6 -0
- package/src/utils/Language.ts +70 -0
- package/src/utils/difficultyConverter.ts +4 -0
- package/src/worker/WorkerSetup.ts +5 -4
- package/dist/components/avatar/Assistant.d.ts +0 -9
- package/dist/components/avatar/Assistant.js +0 -59
- package/dist/components/avatar/Avatar.d.ts +0 -12
- package/dist/components/avatar/Avatar.js +0 -42
- package/dist/components/avatar/EmbeddedAssistent/AudioInputField.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/AudioInputField.js +0 -38
- package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.js +0 -59
- package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
- package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.js +0 -84
- package/dist/components/avatar/EmbeddedAssistent/TTS/Player.d.ts +0 -25
- package/dist/components/avatar/EmbeddedAssistent/TTS/Player.js +0 -180
- package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.js +0 -45
- package/dist/components/avatar/utils.d.ts +0 -6
- 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 =
|
|
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[] = []):
|
|
132
|
-
|
|
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>)):
|
|
160
|
-
const
|
|
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, {
|
|
166
|
-
return
|
|
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
|
|
191
|
+
let listener: EventListener | undefined;
|
|
181
192
|
const wrapper = (event: EventBusMessage<T>) => {
|
|
182
193
|
handler(event);
|
|
183
|
-
|
|
194
|
+
listener?.off();
|
|
184
195
|
};
|
|
185
|
-
|
|
196
|
+
listener = this.on(topic, wrapper);
|
|
186
197
|
|
|
187
|
-
this.logIfDebug(`Added once listener ` + 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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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,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('
|
|
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('
|
|
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('
|
|
52
|
+
logIfDebug('Worker initialized.');
|
|
52
53
|
await init(controller);
|
|
53
|
-
logIfDebug('
|
|
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
|
-
;
|