@rimori/client 1.2.0 → 1.3.1
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 +61 -18
- package/dist/cli/scripts/init/dev-registration.js +0 -1
- package/dist/cli/scripts/init/main.d.ts +1 -1
- package/dist/cli/scripts/init/main.js +1 -0
- package/dist/components/LoggerExample.d.ts +6 -0
- package/dist/components/LoggerExample.js +79 -0
- package/dist/components/ai/Assistant.js +2 -2
- package/dist/components/ai/Avatar.js +2 -2
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -32
- package/dist/components/audio/Playbutton.js +2 -2
- package/dist/components/components/ContextMenu.js +48 -9
- package/dist/core/controller/AIController.js +202 -69
- package/dist/core/controller/AudioController.d.ts +0 -0
- package/dist/core/controller/AudioController.js +1 -0
- package/dist/core/controller/ObjectController.d.ts +2 -2
- package/dist/core/controller/ObjectController.js +8 -8
- package/dist/core/controller/SettingsController.d.ts +16 -0
- package/dist/core/controller/SharedContentController.d.ts +30 -2
- package/dist/core/controller/SharedContentController.js +74 -23
- package/dist/core/controller/VoiceController.d.ts +2 -3
- package/dist/core/controller/VoiceController.js +11 -4
- package/dist/core/core.d.ts +1 -0
- package/dist/fromRimori/EventBus.js +1 -1
- package/dist/fromRimori/PluginTypes.d.ts +7 -4
- package/dist/hooks/UseChatHook.js +6 -4
- package/dist/hooks/UseLogger.d.ts +30 -0
- package/dist/hooks/UseLogger.js +122 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin/AudioController.d.ts +37 -0
- package/dist/plugin/AudioController.js +68 -0
- package/dist/plugin/Logger.d.ts +68 -0
- package/dist/plugin/Logger.js +256 -0
- package/dist/plugin/LoggerExample.d.ts +16 -0
- package/dist/plugin/LoggerExample.js +140 -0
- package/dist/plugin/PluginController.d.ts +15 -3
- package/dist/plugin/PluginController.js +162 -39
- package/dist/plugin/RimoriClient.d.ts +55 -13
- package/dist/plugin/RimoriClient.js +60 -23
- package/dist/plugin/StandaloneClient.d.ts +1 -0
- package/dist/plugin/StandaloneClient.js +16 -5
- package/dist/plugin/ThemeSetter.d.ts +2 -2
- package/dist/plugin/ThemeSetter.js +8 -5
- package/dist/providers/PluginProvider.d.ts +1 -1
- package/dist/providers/PluginProvider.js +36 -10
- package/dist/utils/audioFormats.d.ts +26 -0
- package/dist/utils/audioFormats.js +67 -0
- package/dist/worker/WorkerSetup.d.ts +3 -2
- package/dist/worker/WorkerSetup.js +22 -67
- package/package.json +7 -6
- package/src/cli/scripts/init/dev-registration.ts +0 -1
- package/src/cli/scripts/init/main.ts +1 -0
- package/src/components/ai/Assistant.tsx +2 -2
- package/src/components/ai/Avatar.tsx +2 -2
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +39 -32
- package/src/components/audio/Playbutton.tsx +2 -2
- package/src/components/components/ContextMenu.tsx +53 -9
- package/src/core/controller/AIController.ts +236 -75
- package/src/core/controller/ObjectController.ts +8 -8
- package/src/core/controller/SettingsController.ts +16 -0
- package/src/core/controller/SharedContentController.ts +87 -25
- package/src/core/controller/VoiceController.ts +24 -19
- package/src/core/core.ts +1 -0
- package/src/fromRimori/EventBus.ts +1 -1
- package/src/fromRimori/PluginTypes.ts +6 -4
- package/src/hooks/UseChatHook.ts +6 -4
- package/src/index.ts +1 -0
- package/src/plugin/AudioController.ts +58 -0
- package/src/plugin/Logger.ts +324 -0
- package/src/plugin/PluginController.ts +171 -43
- package/src/plugin/RimoriClient.ts +95 -30
- package/src/plugin/StandaloneClient.ts +22 -6
- package/src/plugin/ThemeSetter.ts +8 -5
- package/src/providers/PluginProvider.tsx +40 -10
- package/src/worker/WorkerSetup.ts +14 -63
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { generateText, streamChatGPT } from "../core/controller/AIController";
|
|
11
|
-
import { generateObject
|
|
11
|
+
import { generateObject } from "../core/controller/ObjectController";
|
|
12
12
|
import { SettingsController } from "../core/controller/SettingsController";
|
|
13
13
|
import { SharedContentController } from "../core/controller/SharedContentController";
|
|
14
14
|
import { getSTTResponse, getTTSResponse } from "../core/controller/VoiceController";
|
|
@@ -91,6 +91,11 @@ export class RimoriClient {
|
|
|
91
91
|
*/
|
|
92
92
|
emitSidebarAction: (pluginId, actionKey, text) => {
|
|
93
93
|
this.event.emit("global.sidebar.triggerAction", { plugin_id: pluginId, action_key: actionKey, text });
|
|
94
|
+
},
|
|
95
|
+
onMainPanelAction: (callback) => {
|
|
96
|
+
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
97
|
+
this.event.emit("action.requestMain");
|
|
98
|
+
this.event.on("action.requestMain", ({ data }) => callback(data));
|
|
94
99
|
}
|
|
95
100
|
};
|
|
96
101
|
this.navigation = {
|
|
@@ -109,17 +114,24 @@ export class RimoriClient {
|
|
|
109
114
|
}),
|
|
110
115
|
getVoice: (text_1, ...args_1) => __awaiter(this, [text_1, ...args_1], void 0, function* (text, voice = "alloy", speed = 1, language) {
|
|
111
116
|
const token = yield this.pluginController.getToken();
|
|
112
|
-
return getTTSResponse(this.
|
|
117
|
+
return getTTSResponse(this.pluginController.getBackendUrl(), { input: text, voice, speed, language }, token);
|
|
118
|
+
}),
|
|
119
|
+
getTextFromVoice: (file) => __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
const token = yield this.pluginController.getToken();
|
|
121
|
+
return getSTTResponse(this.pluginController.getBackendUrl(), file, token);
|
|
113
122
|
}),
|
|
114
|
-
getTextFromVoice: (file) => {
|
|
115
|
-
return getSTTResponse(this.superbase, file);
|
|
116
|
-
},
|
|
117
123
|
getObject: (request) => __awaiter(this, void 0, void 0, function* () {
|
|
118
124
|
const token = yield this.pluginController.getToken();
|
|
119
|
-
return
|
|
125
|
+
return generateObject(this.pluginController.getBackendUrl(), request, token);
|
|
120
126
|
}),
|
|
121
127
|
// getSteamedObject: this.generateObjectStream,
|
|
122
128
|
};
|
|
129
|
+
this.runtime = {
|
|
130
|
+
fetchBackend: (url, options) => __awaiter(this, void 0, void 0, function* () {
|
|
131
|
+
const token = yield this.pluginController.getToken();
|
|
132
|
+
return fetch(this.pluginController.getBackendUrl() + url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { 'Authorization': `Bearer ${token}` }) }));
|
|
133
|
+
})
|
|
134
|
+
};
|
|
123
135
|
this.community = {
|
|
124
136
|
/**
|
|
125
137
|
* Shared content is a way to share completable content with other users using this plugin.
|
|
@@ -151,11 +163,15 @@ export class RimoriClient {
|
|
|
151
163
|
* @param contentType The type of shared content to fetch. E.g. assignments, exercises, etc.
|
|
152
164
|
* @param generatorInstructions The instructions for the creation of new shared content. The object will automatically be extended with a tool property with a topic and keywords property to let a new unique topic be generated.
|
|
153
165
|
* @param filter The optional additional filter for checking new shared content based on a column and value. This is useful if the aditional information stored on the shared content is used to further narrow down the kind of shared content wanted to be received. E.g. only adjective grammar exercises.
|
|
154
|
-
* @param
|
|
166
|
+
* @param options An optional object with options for the new shared content.
|
|
167
|
+
* @param options.privateTopic An optional flag to indicate if the topic should be private and only be visible to the user. This is useful if the topic is not meant to be shared with other users. Like for personal topics or if the content is based on the personal study goal.
|
|
168
|
+
* @param options.skipDbSave An optional flag to indicate if the new shared content should not be saved to the database. This is useful if the new shared content is not meant to be saved to the database.
|
|
169
|
+
* @param options.alwaysGenerateNew An optional flag to indicate if the new shared content should always be generated even if there is already a content with the same filter. This is useful if the new shared content is not meant to be saved to the database.
|
|
170
|
+
* @param options.excludeIds An optional list of ids to exclude from the selection. This is useful if the new shared content is not meant to be saved to the database.
|
|
155
171
|
* @returns The new shared content.
|
|
156
172
|
*/
|
|
157
|
-
getNew: (contentType, generatorInstructions, filter,
|
|
158
|
-
return yield this.sharedContentController.getNewSharedContent(contentType, generatorInstructions, filter,
|
|
173
|
+
getNew: (contentType, generatorInstructions, filter, options) => __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
return yield this.sharedContentController.getNewSharedContent(contentType, generatorInstructions, filter, options);
|
|
159
175
|
}),
|
|
160
176
|
/**
|
|
161
177
|
* Create a new shared content item.
|
|
@@ -182,6 +198,14 @@ export class RimoriClient {
|
|
|
182
198
|
complete: (contentType, assignmentId) => __awaiter(this, void 0, void 0, function* () {
|
|
183
199
|
return yield this.sharedContentController.completeSharedContent(contentType, assignmentId);
|
|
184
200
|
}),
|
|
201
|
+
/**
|
|
202
|
+
/**
|
|
203
|
+
* Update the state of a shared content item for a specific user.
|
|
204
|
+
* Useful for marking content as completed, ongoing, hidden, liked, disliked, or bookmarked.
|
|
205
|
+
*/
|
|
206
|
+
updateState: (params) => __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
return yield this.sharedContentController.updateSharedContentState(params);
|
|
208
|
+
}),
|
|
185
209
|
/**
|
|
186
210
|
* Remove a shared content item.
|
|
187
211
|
* @param id The id of the shared content item to remove.
|
|
@@ -192,18 +216,16 @@ export class RimoriClient {
|
|
|
192
216
|
})
|
|
193
217
|
}
|
|
194
218
|
};
|
|
219
|
+
this.rimoriInfo = info;
|
|
195
220
|
this.superbase = supabase;
|
|
196
221
|
this.pluginController = pluginController;
|
|
197
222
|
this.settingsController = new SettingsController(supabase, info.pluginId);
|
|
198
223
|
this.sharedContentController = new SharedContentController(this.superbase, this);
|
|
199
|
-
this.supabaseUrl = this.pluginController.getSupabaseUrl();
|
|
200
224
|
this.accomplishmentHandler = new AccomplishmentHandler(info.pluginId);
|
|
201
|
-
this.installedPlugins = info.installedPlugins;
|
|
202
|
-
this.profile = info.profile;
|
|
203
225
|
this.from = this.from.bind(this);
|
|
204
226
|
this.db = {
|
|
205
227
|
from: this.from,
|
|
206
|
-
storage: this.superbase.storage,
|
|
228
|
+
// storage: this.superbase.storage,
|
|
207
229
|
// functions: this.superbase.functions,
|
|
208
230
|
tablePrefix: info.tablePrefix,
|
|
209
231
|
getTableName: this.getTableName.bind(this),
|
|
@@ -216,14 +238,26 @@ export class RimoriClient {
|
|
|
216
238
|
getSettings: (defaultSettings) => __awaiter(this, void 0, void 0, function* () {
|
|
217
239
|
return yield this.settingsController.getSettings(defaultSettings);
|
|
218
240
|
}),
|
|
219
|
-
|
|
220
|
-
return this.
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return
|
|
224
|
-
|
|
241
|
+
getUserInfo: () => {
|
|
242
|
+
return this.rimoriInfo.profile;
|
|
243
|
+
},
|
|
244
|
+
getPluginInfo: () => {
|
|
245
|
+
return {
|
|
246
|
+
installedPlugins: this.rimoriInfo.installedPlugins,
|
|
247
|
+
mainPanelPlugin: this.rimoriInfo.mainPanelPlugin,
|
|
248
|
+
sidePanelPlugin: this.rimoriInfo.sidePanelPlugin,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
225
251
|
};
|
|
226
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Get a query parameter value that was passed via MessageChannel
|
|
255
|
+
* @param key The query parameter key
|
|
256
|
+
* @returns The query parameter value or null if not found
|
|
257
|
+
*/
|
|
258
|
+
getQueryParam(key) {
|
|
259
|
+
return this.pluginController.getQueryParam(key);
|
|
260
|
+
}
|
|
227
261
|
static getInstance(pluginController) {
|
|
228
262
|
return __awaiter(this, void 0, void 0, function* () {
|
|
229
263
|
if (!RimoriClient.instance) {
|
|
@@ -236,10 +270,13 @@ export class RimoriClient {
|
|
|
236
270
|
from(relation) {
|
|
237
271
|
return this.superbase.from(this.getTableName(relation));
|
|
238
272
|
}
|
|
239
|
-
getTableName(
|
|
240
|
-
if (
|
|
241
|
-
|
|
273
|
+
getTableName(table) {
|
|
274
|
+
if (/[A-Z]/.test(table)) {
|
|
275
|
+
throw new Error("Table name cannot include uppercase letters. Please use snake_case for table names.");
|
|
276
|
+
}
|
|
277
|
+
if (table.startsWith("global_")) {
|
|
278
|
+
return table.replace("global_", "");
|
|
242
279
|
}
|
|
243
|
-
return this.db.tablePrefix + "_" +
|
|
280
|
+
return this.db.tablePrefix + "_" + table;
|
|
244
281
|
}
|
|
245
282
|
}
|
|
@@ -24,6 +24,7 @@ export class StandaloneClient {
|
|
|
24
24
|
StandaloneClient.instance = new StandaloneClient({
|
|
25
25
|
url: (config === null || config === void 0 ? void 0 : config.SUPABASE_URL) || DEFAULT_ENDPOINT,
|
|
26
26
|
key: (config === null || config === void 0 ? void 0 : config.SUPABASE_ANON_KEY) || DEFAULT_ANON_KEY,
|
|
27
|
+
backendUrl: (config === null || config === void 0 ? void 0 : config.BACKEND_URL) || 'https://api.rimori.se',
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
return StandaloneClient.instance;
|
|
@@ -61,18 +62,28 @@ export class StandaloneClient {
|
|
|
61
62
|
var _a;
|
|
62
63
|
const session = yield supabase.auth.getSession();
|
|
63
64
|
console.log("session", session);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// Call the NestJS backend endpoint instead of the Supabase edge function
|
|
66
|
+
const response = yield fetch(`${config.backendUrl}/plugin/token`, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
'Authorization': `Bearer ${(_a = session.data.session) === null || _a === void 0 ? void 0 : _a.access_token}`
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
pluginId: pluginId
|
|
74
|
+
})
|
|
67
75
|
});
|
|
68
|
-
if (
|
|
69
|
-
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorText = yield response.text();
|
|
78
|
+
throw new Error(`Failed to get plugin token. ${response.status}: ${errorText}`);
|
|
70
79
|
}
|
|
80
|
+
const data = yield response.json();
|
|
71
81
|
return {
|
|
72
82
|
token: data.token,
|
|
73
83
|
pluginId: pluginId,
|
|
74
84
|
url: config.url,
|
|
75
85
|
key: config.key,
|
|
86
|
+
backendUrl: config.backendUrl,
|
|
76
87
|
tablePrefix: pluginId,
|
|
77
88
|
expiration: new Date(Date.now() + 1000 * 60 * 60 * 1.5), // 1.5 hours
|
|
78
89
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function setTheme(): void;
|
|
2
|
-
export declare function isDarkTheme(): boolean;
|
|
1
|
+
export declare function setTheme(theme?: string | null): void;
|
|
2
|
+
export declare function isDarkTheme(theme?: string | null): boolean;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
export function setTheme() {
|
|
1
|
+
export function setTheme(theme) {
|
|
2
2
|
document.documentElement.classList.add("dark:text-gray-200");
|
|
3
|
-
if (isDarkTheme()) {
|
|
3
|
+
if (isDarkTheme(theme)) {
|
|
4
4
|
document.documentElement.setAttribute("data-theme", "dark");
|
|
5
5
|
document.documentElement.classList.add('dark', "dark:bg-gray-950");
|
|
6
6
|
document.documentElement.style.background = "hsl(var(--background))";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
export function isDarkTheme() {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export function isDarkTheme(theme) {
|
|
10
|
+
// If no theme provided, try to get from URL as fallback (for standalone mode)
|
|
11
|
+
if (!theme) {
|
|
12
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
13
|
+
theme = urlParams.get('theme');
|
|
14
|
+
}
|
|
12
15
|
if (!theme || theme === 'system') {
|
|
13
16
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
14
17
|
}
|
|
@@ -17,27 +17,40 @@ const PluginContext = createContext(null);
|
|
|
17
17
|
export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
18
18
|
const [plugin, setPlugin] = useState(null);
|
|
19
19
|
const [standaloneClient, setStandaloneClient] = useState(false);
|
|
20
|
+
const [applicationMode, setApplicationMode] = useState(null);
|
|
21
|
+
const [theme, setTheme] = useState(null);
|
|
22
|
+
const isSidebar = applicationMode === "sidebar";
|
|
23
|
+
const isSettings = applicationMode === "settings";
|
|
20
24
|
useEffect(() => {
|
|
21
25
|
initEventBus(pluginId);
|
|
22
|
-
|
|
26
|
+
// Check if we're in an iframe context - if not, we're standalone
|
|
27
|
+
const standaloneDetected = window === window.parent;
|
|
23
28
|
if (standaloneDetected && !standaloneClient) {
|
|
24
29
|
StandaloneClient.getInstance().then(client => {
|
|
25
30
|
client.needsLogin().then((needLogin) => setStandaloneClient(needLogin ? client : true));
|
|
26
31
|
});
|
|
27
32
|
}
|
|
28
33
|
if ((!standaloneDetected && !plugin) || (standaloneDetected && standaloneClient === true)) {
|
|
29
|
-
PluginController.getInstance(pluginId, standaloneDetected).then(
|
|
34
|
+
PluginController.getInstance(pluginId, standaloneDetected).then(client => {
|
|
35
|
+
setPlugin(client);
|
|
36
|
+
// Get applicationMode and theme from MessageChannel query params
|
|
37
|
+
if (!standaloneDetected) {
|
|
38
|
+
const mode = client.getQueryParam("applicationMode");
|
|
39
|
+
const themeParam = client.getQueryParam("rm_theme");
|
|
40
|
+
setApplicationMode(mode);
|
|
41
|
+
setTheme(themeParam);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
30
44
|
}
|
|
31
45
|
}, [pluginId, standaloneClient]);
|
|
32
46
|
//route change
|
|
33
47
|
useEffect(() => {
|
|
34
48
|
if (!plugin)
|
|
35
49
|
return;
|
|
36
|
-
const url = new URL(window.location.href);
|
|
37
50
|
//sidebar pages should not report url changes
|
|
38
|
-
if (
|
|
51
|
+
if (isSidebar)
|
|
39
52
|
return;
|
|
40
|
-
let lastHash =
|
|
53
|
+
let lastHash = window.location.hash;
|
|
41
54
|
const emitUrlChange = (url) => plugin.event.emit('session.triggerUrlChange', { url });
|
|
42
55
|
const interval = setInterval(() => {
|
|
43
56
|
if (lastHash === window.location.hash)
|
|
@@ -66,18 +79,31 @@ export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
|
66
79
|
if (!plugin) {
|
|
67
80
|
return "";
|
|
68
81
|
}
|
|
69
|
-
return (_jsxs(PluginContext.Provider, { value: plugin, children: [!(settings === null || settings === void 0 ? void 0 : settings.disableContextMenu) && _jsx(ContextMenu, { client: plugin }), children] }));
|
|
82
|
+
return (_jsxs(PluginContext.Provider, { value: plugin, children: [!(settings === null || settings === void 0 ? void 0 : settings.disableContextMenu) && !isSidebar && !isSettings && _jsx(ContextMenu, { client: plugin }), children] }));
|
|
70
83
|
};
|
|
71
|
-
export const
|
|
84
|
+
export const useRimori = () => {
|
|
72
85
|
const context = useContext(PluginContext);
|
|
73
86
|
if (context === null) {
|
|
74
|
-
throw new Error('
|
|
87
|
+
throw new Error('useRimori must be used within an PluginProvider');
|
|
75
88
|
}
|
|
76
89
|
return context;
|
|
77
90
|
};
|
|
78
|
-
function
|
|
91
|
+
function getUrlParam(name) {
|
|
92
|
+
// First try to get from URL hash query params (for compatibility)
|
|
93
|
+
const hashParts = window.location.hash.split('?');
|
|
94
|
+
if (hashParts.length > 1) {
|
|
95
|
+
const hashParams = new URLSearchParams(hashParts[1]);
|
|
96
|
+
const hashValue = hashParams.get(name);
|
|
97
|
+
if (hashValue)
|
|
98
|
+
return hashValue;
|
|
99
|
+
}
|
|
100
|
+
// Fallback to regular URL search params
|
|
79
101
|
const url = new URL(window.location.href);
|
|
80
|
-
|
|
102
|
+
return url.searchParams.get(name);
|
|
103
|
+
}
|
|
104
|
+
function initEventBus(pluginId) {
|
|
105
|
+
// For now, use URL fallback for EventBus naming - this will be updated once MessageChannel is ready
|
|
106
|
+
const isSidebar = getUrlParam("applicationMode") === "sidebar";
|
|
81
107
|
EventBusHandler.getInstance("Plugin EventBus " + pluginId + " " + (isSidebar ? "sidebar" : "main"));
|
|
82
108
|
}
|
|
83
109
|
function StandaloneAuth({ onLogin }) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps audio MIME types to their corresponding file extensions
|
|
3
|
+
* OpenAI supports: mp3, mp4, mpeg, mpga, m4a, wav, webm
|
|
4
|
+
*/
|
|
5
|
+
export declare const AUDIO_MIME_TO_EXTENSION: Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* OpenAI supported audio formats for STT API
|
|
8
|
+
*/
|
|
9
|
+
export declare const OPENAI_SUPPORTED_FORMATS: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Determines the appropriate file extension for an audio MIME type
|
|
12
|
+
* @param mimeType - The MIME type of the audio
|
|
13
|
+
* @returns The file extension (without dot) or null if unsupported
|
|
14
|
+
*/
|
|
15
|
+
export declare function getAudioFormatFromMimeType(mimeType: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Checks if an audio MIME type is supported by OpenAI
|
|
18
|
+
* @param mimeType - The MIME type to check
|
|
19
|
+
* @returns True if supported, false otherwise
|
|
20
|
+
*/
|
|
21
|
+
export declare function isAudioFormatSupported(mimeType: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Gets a human-readable list of supported audio formats
|
|
24
|
+
* @returns Formatted string of supported formats
|
|
25
|
+
*/
|
|
26
|
+
export declare function getSupportedFormatsList(): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps audio MIME types to their corresponding file extensions
|
|
3
|
+
* OpenAI supports: mp3, mp4, mpeg, mpga, m4a, wav, webm
|
|
4
|
+
*/
|
|
5
|
+
export const AUDIO_MIME_TO_EXTENSION = {
|
|
6
|
+
'audio/mpeg': 'mp3',
|
|
7
|
+
'audio/mp3': 'mp3',
|
|
8
|
+
'audio/mp4': 'mp4',
|
|
9
|
+
'audio/m4a': 'm4a',
|
|
10
|
+
'audio/wav': 'wav',
|
|
11
|
+
'audio/wave': 'wav',
|
|
12
|
+
'audio/x-wav': 'wav',
|
|
13
|
+
'audio/webm': 'webm',
|
|
14
|
+
'audio/ogg': 'webm', // Convert ogg to webm for OpenAI compatibility
|
|
15
|
+
'audio/aac': 'm4a', // Convert aac to m4a for OpenAI compatibility
|
|
16
|
+
'audio/x-aac': 'm4a',
|
|
17
|
+
'audio/3gpp': 'mp4', // Convert 3gpp to mp4
|
|
18
|
+
'audio/3gpp2': 'mp4',
|
|
19
|
+
'audio/amr': 'mp4', // Convert amr to mp4
|
|
20
|
+
'audio/amr-wb': 'mp4',
|
|
21
|
+
'audio/flac': 'wav', // Convert flac to wav
|
|
22
|
+
'audio/x-flac': 'wav',
|
|
23
|
+
'audio/opus': 'webm', // Convert opus to webm
|
|
24
|
+
'audio/x-opus': 'webm',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI supported audio formats for STT API
|
|
28
|
+
*/
|
|
29
|
+
export const OPENAI_SUPPORTED_FORMATS = ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm'];
|
|
30
|
+
/**
|
|
31
|
+
* Determines the appropriate file extension for an audio MIME type
|
|
32
|
+
* @param mimeType - The MIME type of the audio
|
|
33
|
+
* @returns The file extension (without dot) or null if unsupported
|
|
34
|
+
*/
|
|
35
|
+
export function getAudioFormatFromMimeType(mimeType) {
|
|
36
|
+
const normalizedMimeType = mimeType.toLowerCase();
|
|
37
|
+
// Try to get format from MIME type mapping first
|
|
38
|
+
let format = AUDIO_MIME_TO_EXTENSION[normalizedMimeType];
|
|
39
|
+
// If no mapping found, try to extract from MIME type
|
|
40
|
+
if (!format && normalizedMimeType.includes('audio/')) {
|
|
41
|
+
format = normalizedMimeType.replace('audio/', '');
|
|
42
|
+
}
|
|
43
|
+
// Handle special cases
|
|
44
|
+
if (format === 'mpeg') {
|
|
45
|
+
format = 'mp3'; // OpenAI expects mp3, not mpeg
|
|
46
|
+
}
|
|
47
|
+
// Validate the format
|
|
48
|
+
if (!format || !OPENAI_SUPPORTED_FORMATS.includes(format)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return format;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks if an audio MIME type is supported by OpenAI
|
|
55
|
+
* @param mimeType - The MIME type to check
|
|
56
|
+
* @returns True if supported, false otherwise
|
|
57
|
+
*/
|
|
58
|
+
export function isAudioFormatSupported(mimeType) {
|
|
59
|
+
return getAudioFormatFromMimeType(mimeType) !== null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Gets a human-readable list of supported audio formats
|
|
63
|
+
* @returns Formatted string of supported formats
|
|
64
|
+
*/
|
|
65
|
+
export function getSupportedFormatsList() {
|
|
66
|
+
return OPENAI_SUPPORTED_FORMATS.join(', ');
|
|
67
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RimoriClient } from "../plugin/RimoriClient";
|
|
2
2
|
/**
|
|
3
3
|
* Sets up the web worker for the plugin to be able receive and send messages to Rimori.
|
|
4
|
-
* @param
|
|
4
|
+
* @param pluginId - The id of the plugin to setup the worker for.
|
|
5
|
+
* @param init - The function containing the initialization logic.
|
|
5
6
|
*/
|
|
6
|
-
export declare function setupWorker(init: (
|
|
7
|
+
export declare function setupWorker(pluginId: string, init: (client: RimoriClient) => void | Promise<void>): Promise<void>;
|
|
@@ -7,76 +7,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { EventBusHandler } from "../fromRimori/EventBus";
|
|
11
11
|
import { PluginController } from "../plugin/PluginController";
|
|
12
|
-
let controller = null;
|
|
13
|
-
const listeners = [];
|
|
14
|
-
let debugEnabled = false;
|
|
15
12
|
/**
|
|
16
13
|
* Sets up the web worker for the plugin to be able receive and send messages to Rimori.
|
|
17
|
-
* @param
|
|
14
|
+
* @param pluginId - The id of the plugin to setup the worker for.
|
|
15
|
+
* @param init - The function containing the initialization logic.
|
|
18
16
|
*/
|
|
19
|
-
export function setupWorker(init) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
BACKEND_URL: 'NOT_SET',
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
// Assign the mock to globalThis.
|
|
42
|
-
Object.assign(globalThis, { window: mockWindow });
|
|
43
|
-
EventBusHandler.getInstance("Worker EventBus");
|
|
44
|
-
// Handle init message from Rimori.
|
|
45
|
-
self.onmessage = (response) => __awaiter(this, void 0, void 0, function* () {
|
|
46
|
-
checkDebugMode(response.data);
|
|
47
|
-
logIfDebug('Message received', response.data);
|
|
48
|
-
const event = response.data;
|
|
49
|
-
if (event.topic === 'global.worker.requestInit') {
|
|
50
|
-
if (!controller) {
|
|
51
|
-
mockWindow.APP_CONFIG.SUPABASE_URL = event.data.supabaseUrl;
|
|
52
|
-
mockWindow.APP_CONFIG.SUPABASE_ANON_KEY = event.data.supabaseAnonKey;
|
|
53
|
-
mockWindow.APP_CONFIG.BACKEND_URL = event.data.backendUrl;
|
|
54
|
-
controller = yield PluginController.getInstance(event.data.pluginId);
|
|
55
|
-
logIfDebug('Worker initialized.');
|
|
56
|
-
yield init(controller);
|
|
57
|
-
logIfDebug('Plugin listeners initialized.');
|
|
58
|
-
}
|
|
59
|
-
const initEvent = {
|
|
60
|
-
timestamp: new Date().toISOString(),
|
|
61
|
-
eventId: event.eventId,
|
|
62
|
-
sender: "worker." + event.sender,
|
|
63
|
-
topic: 'global.worker.requestInit',
|
|
64
|
-
data: { success: true },
|
|
65
|
-
debug: debugEnabled
|
|
66
|
-
};
|
|
67
|
-
return self.postMessage({ secret: "123", event: initEvent });
|
|
68
|
-
}
|
|
69
|
-
listeners.forEach(listener => listener({ data: { event: response.data, secret: "123" } }));
|
|
17
|
+
export function setupWorker(pluginId, init) {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
// Mock of the window object for the worker context to be able to use the PluginController.
|
|
20
|
+
const mockWindow = {
|
|
21
|
+
isWorker: true,
|
|
22
|
+
location: {},
|
|
23
|
+
parent: {
|
|
24
|
+
postMessage: () => { }
|
|
25
|
+
},
|
|
26
|
+
addEventListener: () => { }
|
|
27
|
+
};
|
|
28
|
+
// Assign the mock to globalThis.
|
|
29
|
+
Object.assign(globalThis, { window: mockWindow });
|
|
30
|
+
EventBusHandler.getInstance("Worker EventBus");
|
|
31
|
+
const rimoriClient = yield PluginController.getInstance(pluginId);
|
|
32
|
+
console.debug('[Worker] RimoriClient initialized.');
|
|
33
|
+
yield init(rimoriClient);
|
|
34
|
+
console.debug('[Worker] Worker initialized.');
|
|
35
|
+
self.postMessage({ type: "rimori:acknowledged", pluginId: pluginId });
|
|
70
36
|
});
|
|
71
37
|
}
|
|
72
|
-
function checkDebugMode(event) {
|
|
73
|
-
if (event.topic === 'global.system.requestDebug' || event.debug) {
|
|
74
|
-
debugEnabled = true;
|
|
75
|
-
EventBus.emit("worker", "global.system.requestDebug");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function logIfDebug(...args) {
|
|
79
|
-
if (debugEnabled) {
|
|
80
|
-
console.debug('[Worker] ' + args[0], ...args.slice(1));
|
|
81
|
-
}
|
|
82
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"bin": {
|
|
@@ -32,15 +32,16 @@
|
|
|
32
32
|
"react-dom": "^18.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@supabase/supabase-js": "
|
|
35
|
+
"@supabase/supabase-js": "2.49.4",
|
|
36
36
|
"@tiptap/react": "2.10.3",
|
|
37
37
|
"@tiptap/starter-kit": "2.10.3",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"dotenv": "16.5.0",
|
|
39
|
+
"html2canvas": "1.4.1",
|
|
40
|
+
"react-icons": "5.4.0",
|
|
41
|
+
"react-markdown": "10.1.0",
|
|
42
|
+
"tiptap-markdown": "0.8.10"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
|
-
"dotenv": "^16.5.0",
|
|
44
45
|
"form-data": "^4.0.2",
|
|
45
46
|
"node-fetch": "^3.3.2",
|
|
46
47
|
"sass": "^1.82.0",
|
|
@@ -157,7 +157,6 @@ export async function registerDeveloper(jwtToken: string, port: number): Promise
|
|
|
157
157
|
console.log('🚀 Registering developer and creating plugin...');
|
|
158
158
|
|
|
159
159
|
try {
|
|
160
|
-
console.log('port', port, typeof port);
|
|
161
160
|
const currentFolderName = path.basename(process.cwd());
|
|
162
161
|
const body: any = { port, pluginName: currentFolderName };
|
|
163
162
|
const backendUrl = process.env.RIMORI_BACKEND_URL || "https://api.rimori.se";
|
|
@@ -15,6 +15,7 @@ import { updatePackageJson, type PackageJson } from './package-setup.js';
|
|
|
15
15
|
import { transformAppRouter } from './router-transformer.js';
|
|
16
16
|
import { updateTailwindConfig } from './tailwind-config.js';
|
|
17
17
|
import { updateViteConfigBase } from './vite-config.js';
|
|
18
|
+
import 'dotenv/config';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Main function that handles the complete plugin setup flow.
|
|
@@ -4,7 +4,7 @@ import { AudioInputField } from './EmbeddedAssistent/AudioInputField';
|
|
|
4
4
|
import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
|
|
5
5
|
import Markdown from 'react-markdown';
|
|
6
6
|
import { useChat } from '../../hooks/UseChatHook';
|
|
7
|
-
import {
|
|
7
|
+
import { useRimori } from '../../components';
|
|
8
8
|
import { FirstMessages, getFirstMessages } from './utils';
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
|
|
17
17
|
export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }: Props) {
|
|
18
18
|
const [oralCommunication, setOralCommunication] = React.useState(true);
|
|
19
|
-
const { ai: llm, event } =
|
|
19
|
+
const { ai: llm, event } = useRimori();
|
|
20
20
|
const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
|
|
21
21
|
const { messages, append, isLoading, setMessages } = useChat();
|
|
22
22
|
|
|
@@ -4,7 +4,7 @@ import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
|
|
|
4
4
|
import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
|
|
5
5
|
import { Tool } from '../../fromRimori/PluginTypes';
|
|
6
6
|
import { useChat } from '../../hooks/UseChatHook';
|
|
7
|
-
import {
|
|
7
|
+
import { useRimori } from '../../components';
|
|
8
8
|
import { getFirstMessages } from './utils';
|
|
9
9
|
import { FirstMessages } from './utils';
|
|
10
10
|
|
|
@@ -29,7 +29,7 @@ export function Avatar({
|
|
|
29
29
|
circleSize = "300px",
|
|
30
30
|
className
|
|
31
31
|
}: Props) {
|
|
32
|
-
const { ai, event } =
|
|
32
|
+
const { ai, event } = useRimori();
|
|
33
33
|
const [agentReplying, setAgentReplying] = useState(false);
|
|
34
34
|
const [isProcessingMessage, setIsProcessingMessage] = useState(false);
|
|
35
35
|
const sender = useMemo(() => new MessageSender(ai.getVoice, voiceId), [voiceId]);
|