@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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import { UserInfo } from '../core/controller/SettingsController';
|
|
3
3
|
import { EventBus, EventBusMessage } from '../fromRimori/EventBus';
|
|
4
|
-
import { Plugin } from '../fromRimori/PluginTypes';
|
|
4
|
+
import { ActivePlugin, Plugin } from '../fromRimori/PluginTypes';
|
|
5
5
|
import { RimoriClient } from "./RimoriClient";
|
|
6
6
|
import { StandaloneClient } from './StandaloneClient';
|
|
7
7
|
import { setTheme } from './ThemeSetter';
|
|
8
|
+
import { Logger } from './Logger';
|
|
8
9
|
|
|
9
10
|
// Add declaration for WorkerGlobalScope
|
|
10
11
|
declare const WorkerGlobalScope: any;
|
|
@@ -19,46 +20,116 @@ export interface RimoriInfo {
|
|
|
19
20
|
pluginId: string
|
|
20
21
|
installedPlugins: Plugin[]
|
|
21
22
|
profile: UserInfo
|
|
23
|
+
mainPanelPlugin?: ActivePlugin
|
|
24
|
+
sidePanelPlugin?: ActivePlugin
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export class PluginController {
|
|
25
28
|
private static client: RimoriClient;
|
|
26
29
|
private static instance: PluginController;
|
|
27
|
-
private
|
|
30
|
+
private port: MessagePort | null = null;
|
|
31
|
+
private queryParams: Record<string, string> = {};
|
|
28
32
|
private supabase: SupabaseClient | null = null;
|
|
29
33
|
private rimoriInfo: RimoriInfo | null = null;
|
|
30
34
|
private pluginId: string;
|
|
35
|
+
private isMessageChannelReady: boolean = false;
|
|
36
|
+
private pendingRequests: Array<() => void> = [];
|
|
31
37
|
|
|
32
38
|
private constructor(pluginId: string, standalone: boolean) {
|
|
33
39
|
this.pluginId = pluginId;
|
|
34
40
|
this.getClient = this.getClient.bind(this);
|
|
35
41
|
|
|
36
42
|
if (typeof WorkerGlobalScope === 'undefined') {
|
|
37
|
-
|
|
43
|
+
// In standalone mode, use URL fallback. In iframe mode, theme will be set after MessageChannel init
|
|
44
|
+
if (standalone) {
|
|
45
|
+
setTheme();
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
48
|
|
|
40
|
-
//no need to forward messages to parent in standalone mode
|
|
49
|
+
//no need to forward messages to parent in standalone mode or worker context
|
|
41
50
|
if (standalone) return;
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
this.initMessageChannel(typeof WorkerGlobalScope !== 'undefined');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private initMessageChannel(worker: boolean = false) {
|
|
56
|
+
const listener = (event: MessageEvent) => {
|
|
57
|
+
console.log("[PluginController] window message", { origin: event.origin, data: event.data });
|
|
58
|
+
const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
|
|
59
|
+
const [transferredPort] = event.ports || [];
|
|
60
|
+
|
|
61
|
+
if (type !== "rimori:init" || !transferredPort || pluginId !== this.pluginId) {
|
|
62
|
+
console.log("[PluginController] message ignored (not init or wrong plugin)", { type, pluginId, hasPort: !!transferredPort });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.queryParams = queryParams || {};
|
|
67
|
+
this.port = transferredPort;
|
|
68
|
+
|
|
69
|
+
// Initialize Supabase client immediately with provided info
|
|
70
|
+
if (rimoriInfo) {
|
|
71
|
+
this.rimoriInfo = rimoriInfo;
|
|
72
|
+
this.supabase = createClient(rimoriInfo.url, rimoriInfo.key, {
|
|
73
|
+
accessToken: () => Promise.resolve(rimoriInfo.token)
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Handle messages from parent
|
|
78
|
+
this.port.onmessage = ({ data }) => {
|
|
79
|
+
const { event, type, eventId, response, error } = data || {};
|
|
80
|
+
|
|
81
|
+
// no idea why this is needed but it works for now
|
|
82
|
+
if (type === 'response' && eventId) {
|
|
83
|
+
EventBus.emit(this.pluginId, response.topic, response.data, eventId);
|
|
84
|
+
} else if (type === 'error' && eventId) {
|
|
85
|
+
EventBus.emit(this.pluginId, 'error', { error }, eventId);
|
|
86
|
+
} else if (event) {
|
|
87
|
+
const { topic, sender, data: eventData, eventId } = event as EventBusMessage;
|
|
88
|
+
if (sender !== this.pluginId) {
|
|
89
|
+
EventBus.emit(sender, topic, eventData, eventId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
46
93
|
|
|
47
|
-
//
|
|
48
|
-
if (
|
|
94
|
+
// Set theme from MessageChannel query params
|
|
95
|
+
if (!worker) {
|
|
96
|
+
const theme = this.queryParams['rm_theme'];
|
|
97
|
+
setTheme(theme);
|
|
98
|
+
}
|
|
49
99
|
|
|
50
|
-
|
|
51
|
-
|
|
100
|
+
// Forward plugin events to parent (only after MessageChannel is ready)
|
|
101
|
+
EventBus.on("*", (ev) => {
|
|
102
|
+
if (ev.sender === this.pluginId && !ev.topic.startsWith("self.")) {
|
|
103
|
+
this.port?.postMessage({ event: ev });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
52
106
|
|
|
53
|
-
|
|
107
|
+
// Mark MessageChannel as ready and process pending requests
|
|
108
|
+
this.isMessageChannelReady = true;
|
|
54
109
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
110
|
+
// Process any pending requests
|
|
111
|
+
this.pendingRequests.forEach(request => request());
|
|
112
|
+
this.pendingRequests = [];
|
|
113
|
+
};
|
|
114
|
+
if (worker) {
|
|
115
|
+
self.onmessage = listener;
|
|
116
|
+
} else {
|
|
117
|
+
window.addEventListener("message", listener);
|
|
118
|
+
}
|
|
119
|
+
this.sendHello(worker);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private sendHello(isWorker: boolean = false) {
|
|
123
|
+
try {
|
|
124
|
+
const payload = { type: "rimori:hello", pluginId: this.pluginId };
|
|
125
|
+
if (isWorker) {
|
|
126
|
+
self.postMessage(payload);
|
|
127
|
+
} else {
|
|
128
|
+
window.parent.postMessage(payload, "*");
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error("[PluginController] Error sending hello:", e);
|
|
132
|
+
}
|
|
62
133
|
}
|
|
63
134
|
|
|
64
135
|
public static async getInstance(pluginId: string, standalone = false): Promise<RimoriClient> {
|
|
@@ -68,56 +139,113 @@ export class PluginController {
|
|
|
68
139
|
}
|
|
69
140
|
PluginController.instance = new PluginController(pluginId, standalone);
|
|
70
141
|
PluginController.client = await RimoriClient.getInstance(PluginController.instance);
|
|
142
|
+
|
|
143
|
+
//only init logger in workers and on main plugin pages
|
|
144
|
+
if (PluginController.instance.getQueryParam("applicationMode") !== "sidebar") {
|
|
145
|
+
Logger.getInstance(PluginController.client);
|
|
146
|
+
}
|
|
71
147
|
}
|
|
72
148
|
return PluginController.client;
|
|
73
149
|
}
|
|
74
150
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const secret = new URLSearchParams(window.location.search).get("secret");
|
|
78
|
-
if (!secret) {
|
|
79
|
-
console.info("Communication secret not found in URL as query parameter");
|
|
80
|
-
}
|
|
81
|
-
this.communicationSecret = secret;
|
|
82
|
-
}
|
|
83
|
-
return this.communicationSecret;
|
|
151
|
+
public getQueryParam(key: string): string | null {
|
|
152
|
+
return this.queryParams[key] || null;
|
|
84
153
|
}
|
|
85
154
|
|
|
86
155
|
public async getClient(): Promise<{ supabase: SupabaseClient, info: RimoriInfo }> {
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
this.rimoriInfo &&
|
|
90
|
-
this.rimoriInfo.expiration > new Date()
|
|
91
|
-
) {
|
|
156
|
+
// Return cached client if valid
|
|
157
|
+
if (this.supabase && this.rimoriInfo && this.rimoriInfo.expiration > new Date()) {
|
|
92
158
|
return { supabase: this.supabase, info: this.rimoriInfo };
|
|
93
159
|
}
|
|
94
160
|
|
|
95
|
-
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
// If MessageChannel is not ready yet, queue the request
|
|
162
|
+
if (!this.isMessageChannelReady) {
|
|
163
|
+
return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
|
|
164
|
+
this.pendingRequests.push(async () => {
|
|
165
|
+
const result = await this.getClient();
|
|
166
|
+
resolve(result);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If we have rimoriInfo from MessageChannel init, use it directly
|
|
172
|
+
if (this.rimoriInfo && this.supabase) {
|
|
173
|
+
return { supabase: this.supabase, info: this.rimoriInfo };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Fallback: request from parent
|
|
177
|
+
if (!this.rimoriInfo) {
|
|
178
|
+
if (typeof WorkerGlobalScope !== 'undefined') {
|
|
179
|
+
// In worker context, send request via self.postMessage to WorkerHandler
|
|
180
|
+
const eventId = Math.floor(Math.random() * 1000000000);
|
|
181
|
+
const requestEvent = {
|
|
182
|
+
event: {
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
eventId,
|
|
185
|
+
sender: this.pluginId,
|
|
186
|
+
topic: 'global.supabase.requestAccess',
|
|
187
|
+
data: {},
|
|
188
|
+
debug: false
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return new Promise<{ supabase: SupabaseClient, info: RimoriInfo }>((resolve) => {
|
|
193
|
+
// Listen for the response
|
|
194
|
+
const originalOnMessage = self.onmessage;
|
|
195
|
+
self.onmessage = (event) => {
|
|
196
|
+
if (event.data?.topic === 'global.supabase.requestAccess' && event.data?.eventId === eventId) {
|
|
197
|
+
this.rimoriInfo = event.data.data;
|
|
198
|
+
this.supabase = createClient(this.rimoriInfo!.url, this.rimoriInfo!.key, {
|
|
199
|
+
accessToken: () => Promise.resolve(this.getToken())
|
|
200
|
+
});
|
|
201
|
+
self.onmessage = originalOnMessage; // Restore original handler
|
|
202
|
+
resolve({ supabase: this.supabase, info: this.rimoriInfo! });
|
|
203
|
+
} else if (originalOnMessage) {
|
|
204
|
+
originalOnMessage.call(self, event);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
100
207
|
|
|
101
|
-
|
|
208
|
+
// Send the request
|
|
209
|
+
self.postMessage(requestEvent);
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
// In main thread context, use EventBus
|
|
213
|
+
const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
|
|
214
|
+
this.rimoriInfo = data;
|
|
215
|
+
this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
|
|
216
|
+
accessToken: () => Promise.resolve(this.getToken())
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { supabase: this.supabase!, info: this.rimoriInfo };
|
|
102
222
|
}
|
|
103
223
|
|
|
104
|
-
public async getToken() {
|
|
224
|
+
public async getToken(): Promise<string> {
|
|
105
225
|
if (this.rimoriInfo && this.rimoriInfo.expiration && this.rimoriInfo.expiration > new Date()) {
|
|
106
226
|
return this.rimoriInfo.token;
|
|
107
227
|
}
|
|
108
228
|
|
|
109
|
-
|
|
110
|
-
|
|
229
|
+
// If we don't have rimoriInfo, request it
|
|
111
230
|
if (!this.rimoriInfo) {
|
|
112
|
-
|
|
231
|
+
const { data } = await EventBus.request<RimoriInfo>(this.pluginId, "global.supabase.requestAccess");
|
|
232
|
+
this.rimoriInfo = data;
|
|
233
|
+
return this.rimoriInfo.token;
|
|
113
234
|
}
|
|
114
235
|
|
|
236
|
+
// If token is expired, request fresh access
|
|
237
|
+
const { data } = await EventBus.request<{ token: string, expiration: Date }>(this.pluginId, "global.supabase.requestAccess");
|
|
115
238
|
this.rimoriInfo.token = data.token;
|
|
116
239
|
this.rimoriInfo.expiration = data.expiration;
|
|
117
240
|
|
|
118
241
|
return this.rimoriInfo.token;
|
|
119
242
|
}
|
|
120
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Gets the Supabase URL.
|
|
246
|
+
* @returns The Supabase URL.
|
|
247
|
+
* @deprecated All endpoints should use the backend URL instead.
|
|
248
|
+
*/
|
|
121
249
|
public getSupabaseUrl() {
|
|
122
250
|
if (!this.rimoriInfo) {
|
|
123
251
|
throw new Error("Supabase info not found");
|
|
@@ -2,12 +2,12 @@ import { PostgrestQueryBuilder } from "@supabase/postgrest-js";
|
|
|
2
2
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
3
3
|
import { GenericSchema } from "@supabase/supabase-js/dist/module/lib/types";
|
|
4
4
|
import { generateText, Message, OnLLMResponse, streamChatGPT } from "../core/controller/AIController";
|
|
5
|
-
import { generateObject
|
|
5
|
+
import { generateObject, ObjectRequest } from "../core/controller/ObjectController";
|
|
6
6
|
import { SettingsController, UserInfo } from "../core/controller/SettingsController";
|
|
7
7
|
import { SharedContent, SharedContentController, SharedContentFilter, SharedContentObjectRequest } from "../core/controller/SharedContentController";
|
|
8
8
|
import { getSTTResponse, getTTSResponse } from "../core/controller/VoiceController";
|
|
9
9
|
import { EventBus, EventBusMessage, EventHandler, EventPayload } from "../fromRimori/EventBus";
|
|
10
|
-
import { Plugin, Tool } from "../fromRimori/PluginTypes";
|
|
10
|
+
import { ActivePlugin, MainPanelAction, Plugin, Tool } from "../fromRimori/PluginTypes";
|
|
11
11
|
import { AccomplishmentHandler, AccomplishmentPayload } from "./AccomplishmentHandler";
|
|
12
12
|
import { PluginController, RimoriInfo } from "./PluginController";
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ interface Db {
|
|
|
17
17
|
<TableName extends string & keyof GenericSchema['Tables'], Table extends GenericSchema['Tables'][TableName]>(relation: TableName): PostgrestQueryBuilder<GenericSchema, Table, TableName>;
|
|
18
18
|
<ViewName extends string & keyof GenericSchema['Views'], View extends GenericSchema['Views'][ViewName]>(relation: ViewName): PostgrestQueryBuilder<GenericSchema, View, ViewName>;
|
|
19
19
|
};
|
|
20
|
-
storage: SupabaseClient["storage"];
|
|
20
|
+
// storage: SupabaseClient["storage"];
|
|
21
21
|
|
|
22
22
|
// functions: SupabaseClient["functions"];
|
|
23
23
|
/**
|
|
@@ -44,11 +44,26 @@ interface PluginInterface {
|
|
|
44
44
|
*/
|
|
45
45
|
getSettings: <T extends object>(defaultSettings: T) => Promise<T>;
|
|
46
46
|
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
* Retrieves information about plugins, including:
|
|
48
|
+
* - All installed plugins
|
|
49
|
+
* - The currently active plugin in the main panel
|
|
50
|
+
* - The currently active plugin in the side panel
|
|
51
|
+
*/
|
|
52
|
+
getPluginInfo: () => {
|
|
53
|
+
/**
|
|
54
|
+
* All installed plugins.
|
|
55
|
+
*/
|
|
56
|
+
installedPlugins: Plugin[],
|
|
57
|
+
/**
|
|
58
|
+
* The plugin that is loaded in the main panel.
|
|
59
|
+
*/
|
|
60
|
+
mainPanelPlugin?: ActivePlugin,
|
|
61
|
+
/**
|
|
62
|
+
* The plugin that is loaded in the side panel.
|
|
63
|
+
*/
|
|
64
|
+
sidePanelPlugin?: ActivePlugin,
|
|
65
|
+
};
|
|
66
|
+
getUserInfo: () => UserInfo;
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
export class RimoriClient {
|
|
@@ -58,27 +73,23 @@ export class RimoriClient {
|
|
|
58
73
|
private settingsController: SettingsController;
|
|
59
74
|
private sharedContentController: SharedContentController;
|
|
60
75
|
private accomplishmentHandler: AccomplishmentHandler;
|
|
61
|
-
private
|
|
62
|
-
private installedPlugins: Plugin[];
|
|
63
|
-
private profile: UserInfo;
|
|
76
|
+
private rimoriInfo: RimoriInfo;
|
|
64
77
|
public plugin: PluginInterface;
|
|
65
78
|
public db: Db;
|
|
66
79
|
|
|
67
80
|
private constructor(supabase: SupabaseClient, info: RimoriInfo, pluginController: PluginController) {
|
|
81
|
+
this.rimoriInfo = info;
|
|
68
82
|
this.superbase = supabase;
|
|
69
83
|
this.pluginController = pluginController;
|
|
70
84
|
this.settingsController = new SettingsController(supabase, info.pluginId);
|
|
71
85
|
this.sharedContentController = new SharedContentController(this.superbase, this);
|
|
72
|
-
this.supabaseUrl = this.pluginController.getSupabaseUrl();
|
|
73
86
|
this.accomplishmentHandler = new AccomplishmentHandler(info.pluginId);
|
|
74
|
-
this.installedPlugins = info.installedPlugins;
|
|
75
|
-
this.profile = info.profile;
|
|
76
87
|
|
|
77
88
|
this.from = this.from.bind(this);
|
|
78
89
|
|
|
79
90
|
this.db = {
|
|
80
91
|
from: this.from,
|
|
81
|
-
storage: this.superbase.storage,
|
|
92
|
+
// storage: this.superbase.storage,
|
|
82
93
|
// functions: this.superbase.functions,
|
|
83
94
|
tablePrefix: info.tablePrefix,
|
|
84
95
|
getTableName: this.getTableName.bind(this),
|
|
@@ -91,11 +102,15 @@ export class RimoriClient {
|
|
|
91
102
|
getSettings: async <T extends object>(defaultSettings: T): Promise<T> => {
|
|
92
103
|
return await this.settingsController.getSettings<T>(defaultSettings);
|
|
93
104
|
},
|
|
94
|
-
|
|
95
|
-
return this.
|
|
105
|
+
getUserInfo: (): UserInfo => {
|
|
106
|
+
return this.rimoriInfo.profile;
|
|
96
107
|
},
|
|
97
|
-
|
|
98
|
-
return
|
|
108
|
+
getPluginInfo: () => {
|
|
109
|
+
return {
|
|
110
|
+
installedPlugins: this.rimoriInfo.installedPlugins,
|
|
111
|
+
mainPanelPlugin: this.rimoriInfo.mainPanelPlugin,
|
|
112
|
+
sidePanelPlugin: this.rimoriInfo.sidePanelPlugin,
|
|
113
|
+
}
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
}
|
|
@@ -175,6 +190,12 @@ export class RimoriClient {
|
|
|
175
190
|
*/
|
|
176
191
|
emitSidebarAction: (pluginId: string, actionKey: string, text?: string) => {
|
|
177
192
|
this.event.emit("global.sidebar.triggerAction", { plugin_id: pluginId, action_key: actionKey, text });
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
onMainPanelAction: (callback: (data: MainPanelAction) => void) => {
|
|
196
|
+
// this needs to be a emit and on because the main panel action is triggered by the user and not by the plugin
|
|
197
|
+
this.event.emit("action.requestMain")
|
|
198
|
+
this.event.on<MainPanelAction>("action.requestMain", ({ data }) => callback(data));
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
201
|
|
|
@@ -184,6 +205,15 @@ export class RimoriClient {
|
|
|
184
205
|
}
|
|
185
206
|
}
|
|
186
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Get a query parameter value that was passed via MessageChannel
|
|
210
|
+
* @param key The query parameter key
|
|
211
|
+
* @returns The query parameter value or null if not found
|
|
212
|
+
*/
|
|
213
|
+
public getQueryParam(key: string): string | null {
|
|
214
|
+
return this.pluginController.getQueryParam(key);
|
|
215
|
+
}
|
|
216
|
+
|
|
187
217
|
public static async getInstance(pluginController: PluginController): Promise<RimoriClient> {
|
|
188
218
|
if (!RimoriClient.instance) {
|
|
189
219
|
const client = await pluginController.getClient();
|
|
@@ -204,11 +234,14 @@ export class RimoriClient {
|
|
|
204
234
|
return this.superbase.from(this.getTableName(relation));
|
|
205
235
|
}
|
|
206
236
|
|
|
207
|
-
private getTableName(
|
|
208
|
-
if (
|
|
209
|
-
|
|
237
|
+
private getTableName(table: string) {
|
|
238
|
+
if (/[A-Z]/.test(table)) {
|
|
239
|
+
throw new Error("Table name cannot include uppercase letters. Please use snake_case for table names.");
|
|
240
|
+
}
|
|
241
|
+
if (table.startsWith("global_")) {
|
|
242
|
+
return table.replace("global_", "");
|
|
210
243
|
}
|
|
211
|
-
return this.db.tablePrefix + "_" +
|
|
244
|
+
return this.db.tablePrefix + "_" + table;
|
|
212
245
|
}
|
|
213
246
|
|
|
214
247
|
public ai = {
|
|
@@ -222,18 +255,32 @@ export class RimoriClient {
|
|
|
222
255
|
},
|
|
223
256
|
getVoice: async (text: string, voice = "alloy", speed = 1, language?: string): Promise<Blob> => {
|
|
224
257
|
const token = await this.pluginController.getToken();
|
|
225
|
-
return getTTSResponse(this.
|
|
258
|
+
return getTTSResponse(this.pluginController.getBackendUrl(), { input: text, voice, speed, language }, token);
|
|
226
259
|
},
|
|
227
|
-
getTextFromVoice: (file: Blob): Promise<string> => {
|
|
228
|
-
|
|
260
|
+
getTextFromVoice: async (file: Blob): Promise<string> => {
|
|
261
|
+
const token = await this.pluginController.getToken();
|
|
262
|
+
return getSTTResponse(this.pluginController.getBackendUrl(), file, token);
|
|
229
263
|
},
|
|
230
264
|
getObject: async (request: ObjectRequest): Promise<any> => {
|
|
231
265
|
const token = await this.pluginController.getToken();
|
|
232
|
-
return
|
|
266
|
+
return generateObject(this.pluginController.getBackendUrl(), request, token);
|
|
233
267
|
},
|
|
234
268
|
// getSteamedObject: this.generateObjectStream,
|
|
235
269
|
}
|
|
236
270
|
|
|
271
|
+
public runtime = {
|
|
272
|
+
fetchBackend: async (url: string, options: RequestInit) => {
|
|
273
|
+
const token = await this.pluginController.getToken();
|
|
274
|
+
return fetch(this.pluginController.getBackendUrl() + url, {
|
|
275
|
+
...options,
|
|
276
|
+
headers: {
|
|
277
|
+
...options.headers,
|
|
278
|
+
'Authorization': `Bearer ${token}`
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
237
284
|
public community = {
|
|
238
285
|
/**
|
|
239
286
|
* Shared content is a way to share completable content with other users using this plugin.
|
|
@@ -265,16 +312,20 @@ export class RimoriClient {
|
|
|
265
312
|
* @param contentType The type of shared content to fetch. E.g. assignments, exercises, etc.
|
|
266
313
|
* @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.
|
|
267
314
|
* @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.
|
|
268
|
-
* @param
|
|
315
|
+
* @param options An optional object with options for the new shared content.
|
|
316
|
+
* @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.
|
|
317
|
+
* @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.
|
|
318
|
+
* @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.
|
|
319
|
+
* @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.
|
|
269
320
|
* @returns The new shared content.
|
|
270
321
|
*/
|
|
271
322
|
getNew: async <T = any>(
|
|
272
323
|
contentType: string,
|
|
273
324
|
generatorInstructions: SharedContentObjectRequest,
|
|
274
325
|
filter?: SharedContentFilter,
|
|
275
|
-
privateTopic?: boolean,
|
|
326
|
+
options?: { privateTopic?: boolean; skipDbSave?: boolean; alwaysGenerateNew?: boolean; excludeIds?: string[] },
|
|
276
327
|
): Promise<SharedContent<T>> => {
|
|
277
|
-
return await this.sharedContentController.getNewSharedContent(contentType, generatorInstructions, filter,
|
|
328
|
+
return await this.sharedContentController.getNewSharedContent(contentType, generatorInstructions, filter, options);
|
|
278
329
|
},
|
|
279
330
|
/**
|
|
280
331
|
* Create a new shared content item.
|
|
@@ -301,6 +352,20 @@ export class RimoriClient {
|
|
|
301
352
|
complete: async (contentType: string, assignmentId: string) => {
|
|
302
353
|
return await this.sharedContentController.completeSharedContent(contentType, assignmentId);
|
|
303
354
|
},
|
|
355
|
+
/**
|
|
356
|
+
/**
|
|
357
|
+
* Update the state of a shared content item for a specific user.
|
|
358
|
+
* Useful for marking content as completed, ongoing, hidden, liked, disliked, or bookmarked.
|
|
359
|
+
*/
|
|
360
|
+
updateState: async (params: {
|
|
361
|
+
contentType: string
|
|
362
|
+
id: string
|
|
363
|
+
state?: 'completed' | 'ongoing' | 'hidden'
|
|
364
|
+
reaction?: 'liked' | 'disliked' | null
|
|
365
|
+
bookmarked?: boolean
|
|
366
|
+
}): Promise<void> => {
|
|
367
|
+
return await this.sharedContentController.updateSharedContentState(params);
|
|
368
|
+
},
|
|
304
369
|
/**
|
|
305
370
|
* Remove a shared content item.
|
|
306
371
|
* @param id The id of the shared content item to remove.
|
|
@@ -4,7 +4,8 @@ import { DEFAULT_ANON_KEY, DEFAULT_ENDPOINT } from "../utils/endpoint";
|
|
|
4
4
|
|
|
5
5
|
export interface StandaloneConfig {
|
|
6
6
|
url: string,
|
|
7
|
-
key: string
|
|
7
|
+
key: string,
|
|
8
|
+
backendUrl?: string
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export class StandaloneClient {
|
|
@@ -25,6 +26,7 @@ export class StandaloneClient {
|
|
|
25
26
|
StandaloneClient.instance = new StandaloneClient({
|
|
26
27
|
url: config?.SUPABASE_URL || DEFAULT_ENDPOINT,
|
|
27
28
|
key: config?.SUPABASE_ANON_KEY || DEFAULT_ANON_KEY,
|
|
29
|
+
backendUrl: config?.BACKEND_URL || 'https://api.rimori.se',
|
|
28
30
|
});
|
|
29
31
|
}
|
|
30
32
|
return StandaloneClient.instance;
|
|
@@ -58,18 +60,32 @@ export class StandaloneClient {
|
|
|
58
60
|
EventBus.respond("standalone", "global.supabase.requestAccess", async () => {
|
|
59
61
|
const session = await supabase.auth.getSession();
|
|
60
62
|
console.log("session", session);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
// Call the NestJS backend endpoint instead of the Supabase edge function
|
|
65
|
+
const response = await fetch(`${config.backendUrl}/plugin/token`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
'Authorization': `Bearer ${session.data.session?.access_token}`
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
pluginId: pluginId
|
|
73
|
+
})
|
|
64
74
|
});
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorText = await response.text();
|
|
78
|
+
throw new Error(`Failed to get plugin token. ${response.status}: ${errorText}`);
|
|
67
79
|
}
|
|
80
|
+
|
|
81
|
+
const data = await response.json();
|
|
82
|
+
|
|
68
83
|
return {
|
|
69
84
|
token: data.token,
|
|
70
85
|
pluginId: pluginId,
|
|
71
86
|
url: config.url,
|
|
72
87
|
key: config.key,
|
|
88
|
+
backendUrl: config.backendUrl,
|
|
73
89
|
tablePrefix: pluginId,
|
|
74
90
|
expiration: new Date(Date.now() + 1000 * 60 * 60 * 1.5), // 1.5 hours
|
|
75
91
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
export function setTheme() {
|
|
1
|
+
export function setTheme(theme?: string | null) {
|
|
2
2
|
document.documentElement.classList.add("dark:text-gray-200");
|
|
3
3
|
|
|
4
|
-
if (isDarkTheme()) {
|
|
4
|
+
if (isDarkTheme(theme)) {
|
|
5
5
|
document.documentElement.setAttribute("data-theme", "dark");
|
|
6
6
|
document.documentElement.classList.add('dark', "dark:bg-gray-950");
|
|
7
7
|
document.documentElement.style.background = "hsl(var(--background))";
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function isDarkTheme(): boolean {
|
|
12
|
-
|
|
11
|
+
export function isDarkTheme(theme?: string | null): boolean {
|
|
12
|
+
// If no theme provided, try to get from URL as fallback (for standalone mode)
|
|
13
|
+
if (!theme) {
|
|
14
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
15
|
+
theme = urlParams.get('theme');
|
|
16
|
+
}
|
|
13
17
|
|
|
14
|
-
let theme = urlParams.get('theme');
|
|
15
18
|
if (!theme || theme === 'system') {
|
|
16
19
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
17
20
|
}
|