@rimori/client 2.5.31 → 2.5.32
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/dist/controller/TranslationController.d.ts +1 -0
- package/dist/controller/TranslationController.js +32 -22
- package/dist/plugin/CommunicationHandler.d.ts +11 -1
- package/dist/plugin/CommunicationHandler.js +23 -1
- package/dist/plugin/RimoriClient.d.ts +2 -2
- package/dist/plugin/RimoriClient.js +7 -10
- package/dist/plugin/module/AIModule.d.ts +8 -9
- package/dist/plugin/module/AIModule.js +19 -48
- package/dist/plugin/module/DbModule.d.ts +1 -1
- package/dist/plugin/module/DbModule.js +5 -14
- package/dist/plugin/module/EventModule.d.ts +1 -3
- package/dist/plugin/module/EventModule.js +16 -19
- package/dist/plugin/module/ExerciseModule.d.ts +17 -3
- package/dist/plugin/module/ExerciseModule.js +33 -15
- package/dist/plugin/module/SharedContentController.js +0 -4
- package/dist/plugin/module/StorageModule.d.ts +3 -3
- package/dist/plugin/module/StorageModule.js +3 -15
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export declare class Translator {
|
|
|
11
11
|
private translationUrl;
|
|
12
12
|
private ai;
|
|
13
13
|
private aiTranslationCache;
|
|
14
|
+
private aiTranslationPending;
|
|
14
15
|
constructor(initialLanguage: string, translationUrl: string, ai: AIModule);
|
|
15
16
|
/**
|
|
16
17
|
* Initialize translator with user's language
|
|
@@ -14,6 +14,7 @@ import { createInstance } from 'i18next';
|
|
|
14
14
|
export class Translator {
|
|
15
15
|
constructor(initialLanguage, translationUrl, ai) {
|
|
16
16
|
this.aiTranslationCache = new Map();
|
|
17
|
+
this.aiTranslationPending = new Map();
|
|
17
18
|
this.currentLanguage = initialLanguage;
|
|
18
19
|
this.initializationState = 'not-inited';
|
|
19
20
|
this.initializationPromise = null;
|
|
@@ -158,29 +159,38 @@ export class Translator {
|
|
|
158
159
|
const cached = this.aiTranslationCache.get(text);
|
|
159
160
|
if (cached)
|
|
160
161
|
return cached;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
162
|
+
const pending = this.aiTranslationPending.get(text);
|
|
163
|
+
if (pending)
|
|
164
|
+
return pending;
|
|
165
|
+
if (!this.ai || this.currentLanguage === 'en')
|
|
166
|
+
return text;
|
|
167
|
+
const promise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
try {
|
|
169
|
+
const response = yield this.ai.getObject({
|
|
170
|
+
prompt: 'global.translator.translate',
|
|
171
|
+
variables: {
|
|
172
|
+
additionalInstructions: additionalInstructions !== null && additionalInstructions !== void 0 ? additionalInstructions : '',
|
|
173
|
+
language: this.currentLanguage,
|
|
174
|
+
text,
|
|
175
|
+
},
|
|
176
|
+
cache: true,
|
|
177
|
+
});
|
|
178
|
+
const translation = response === null || response === void 0 ? void 0 : response.translation;
|
|
179
|
+
if (translation) {
|
|
180
|
+
this.aiTranslationCache.set(text, translation);
|
|
181
|
+
return translation;
|
|
182
|
+
}
|
|
178
183
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.warn('Failed to translate freeform text:', { text, error });
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
this.aiTranslationPending.delete(text);
|
|
189
|
+
}
|
|
190
|
+
return text;
|
|
191
|
+
}))();
|
|
192
|
+
this.aiTranslationPending.set(text, promise);
|
|
193
|
+
return promise;
|
|
184
194
|
});
|
|
185
195
|
}
|
|
186
196
|
}
|
|
@@ -46,11 +46,11 @@ export interface RimoriInfo {
|
|
|
46
46
|
ttsEnabled: boolean;
|
|
47
47
|
}
|
|
48
48
|
export declare class RimoriCommunicationHandler {
|
|
49
|
+
readonly pluginId: string;
|
|
49
50
|
private port;
|
|
50
51
|
private queryParams;
|
|
51
52
|
private supabase;
|
|
52
53
|
private rimoriInfo;
|
|
53
|
-
private pluginId;
|
|
54
54
|
private isMessageChannelReady;
|
|
55
55
|
private pendingRequests;
|
|
56
56
|
private updateCallbacks;
|
|
@@ -76,4 +76,14 @@ export declare class RimoriCommunicationHandler {
|
|
|
76
76
|
* @returns Cleanup function to unregister the callback
|
|
77
77
|
*/
|
|
78
78
|
onUpdate(callback: (info: RimoriInfo) => void): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Makes an authenticated fetch request to the Rimori backend.
|
|
81
|
+
* Automatically adds Authorization and plugin-id headers.
|
|
82
|
+
* Content-Type defaults to application/json when the body is a JSON string.
|
|
83
|
+
* Content-Type is omitted for FormData bodies so the browser sets the multipart boundary.
|
|
84
|
+
* Callers can override Content-Type by passing it in options.headers.
|
|
85
|
+
* @param url Path relative to the backend URL (e.g. '/ai/llm')
|
|
86
|
+
* @param options Standard RequestInit options (headers are merged, not replaced)
|
|
87
|
+
*/
|
|
88
|
+
fetchBackend(url: string, options?: RequestInit): Promise<Response>;
|
|
79
89
|
}
|
|
@@ -11,6 +11,7 @@ import { PostgrestClient } from '@supabase/postgrest-js';
|
|
|
11
11
|
import { EventBus } from '../fromRimori/EventBus';
|
|
12
12
|
export class RimoriCommunicationHandler {
|
|
13
13
|
constructor(pluginId, standalone) {
|
|
14
|
+
this.pluginId = pluginId;
|
|
14
15
|
this.port = null;
|
|
15
16
|
this.queryParams = {};
|
|
16
17
|
this.supabase = null;
|
|
@@ -18,7 +19,6 @@ export class RimoriCommunicationHandler {
|
|
|
18
19
|
this.isMessageChannelReady = false;
|
|
19
20
|
this.pendingRequests = [];
|
|
20
21
|
this.updateCallbacks = new Set();
|
|
21
|
-
this.pluginId = pluginId;
|
|
22
22
|
this.getClient = this.getClient.bind(this);
|
|
23
23
|
//no need to forward messages to parent in standalone mode or worker context
|
|
24
24
|
if (standalone)
|
|
@@ -135,6 +135,7 @@ export class RimoriCommunicationHandler {
|
|
|
135
135
|
headers: {
|
|
136
136
|
apikey: key,
|
|
137
137
|
Authorization: `Bearer ${token}`,
|
|
138
|
+
'plugin-id': this.pluginId,
|
|
138
139
|
},
|
|
139
140
|
});
|
|
140
141
|
}
|
|
@@ -238,4 +239,25 @@ export class RimoriCommunicationHandler {
|
|
|
238
239
|
this.updateCallbacks.delete(callback);
|
|
239
240
|
};
|
|
240
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Makes an authenticated fetch request to the Rimori backend.
|
|
244
|
+
* Automatically adds Authorization and plugin-id headers.
|
|
245
|
+
* Content-Type defaults to application/json when the body is a JSON string.
|
|
246
|
+
* Content-Type is omitted for FormData bodies so the browser sets the multipart boundary.
|
|
247
|
+
* Callers can override Content-Type by passing it in options.headers.
|
|
248
|
+
* @param url Path relative to the backend URL (e.g. '/ai/llm')
|
|
249
|
+
* @param options Standard RequestInit options (headers are merged, not replaced)
|
|
250
|
+
*/
|
|
251
|
+
fetchBackend(url, options = {}) {
|
|
252
|
+
if (!this.rimoriInfo) {
|
|
253
|
+
throw new Error(`[CommunicationHandler:${this.pluginId}] fetchBackend called before rimoriInfo was initialized`);
|
|
254
|
+
}
|
|
255
|
+
const { token, backendUrl } = this.rimoriInfo;
|
|
256
|
+
const defaultContentType = {};
|
|
257
|
+
if (typeof options.body === 'string') {
|
|
258
|
+
defaultContentType['Content-Type'] = 'application/json';
|
|
259
|
+
}
|
|
260
|
+
const headers = Object.assign(Object.assign(Object.assign({}, defaultContentType), options.headers), { Authorization: `Bearer ${token}`, 'plugin-id': this.pluginId });
|
|
261
|
+
return fetch(backendUrl + url, Object.assign(Object.assign({}, options), { headers }));
|
|
262
|
+
}
|
|
241
263
|
}
|
|
@@ -9,6 +9,7 @@ import { StorageModule } from './module/StorageModule';
|
|
|
9
9
|
import { EventBusHandler } from '../fromRimori/EventBus';
|
|
10
10
|
export declare class RimoriClient {
|
|
11
11
|
private static instance;
|
|
12
|
+
private controller;
|
|
12
13
|
sharedContent: SharedContentController;
|
|
13
14
|
db: DbModule;
|
|
14
15
|
event: EventModule;
|
|
@@ -17,7 +18,6 @@ export declare class RimoriClient {
|
|
|
17
18
|
exercise: ExerciseModule;
|
|
18
19
|
/** Upload and manage images stored in Supabase via the backend. */
|
|
19
20
|
storage: StorageModule;
|
|
20
|
-
private rimoriInfo;
|
|
21
21
|
/** The EventBus instance used by this client. In federation mode this is a per-plugin instance. */
|
|
22
22
|
eventBus: EventBusHandler;
|
|
23
23
|
private constructor();
|
|
@@ -32,6 +32,6 @@ export declare class RimoriClient {
|
|
|
32
32
|
toDashboard: () => void;
|
|
33
33
|
};
|
|
34
34
|
runtime: {
|
|
35
|
-
fetchBackend: (url: string, options
|
|
35
|
+
fetchBackend: (url: string, options?: RequestInit) => Promise<Response>;
|
|
36
36
|
};
|
|
37
37
|
}
|
|
@@ -26,25 +26,20 @@ export class RimoriClient {
|
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
this.runtime = {
|
|
29
|
-
fetchBackend: (url, options) =>
|
|
30
|
-
return fetch(this.rimoriInfo.backendUrl + url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { Authorization: `Bearer ${this.rimoriInfo.token}` }) }));
|
|
31
|
-
}),
|
|
29
|
+
fetchBackend: (url, options = {}) => this.controller.fetchBackend(url, options),
|
|
32
30
|
};
|
|
33
|
-
this.
|
|
31
|
+
this.controller = controller;
|
|
34
32
|
this.eventBus = eventBus !== null && eventBus !== void 0 ? eventBus : EventBus;
|
|
35
33
|
this.sharedContent = new SharedContentController(supabase, this);
|
|
36
|
-
this.ai = new AIModule(
|
|
34
|
+
this.ai = new AIModule(controller);
|
|
37
35
|
this.ai.setOnRateLimited((exercisesRemaining) => {
|
|
38
36
|
this.eventBus.emit(info.pluginId, 'global.quota.triggerExceeded', { exercises_remaining: exercisesRemaining });
|
|
39
37
|
});
|
|
40
|
-
this.event = new EventModule(info.pluginId,
|
|
38
|
+
this.event = new EventModule(info.pluginId, this.ai, this.eventBus);
|
|
41
39
|
this.db = new DbModule(supabase, controller, info);
|
|
42
40
|
this.plugin = new PluginModule(supabase, controller, info, this.ai);
|
|
43
41
|
this.exercise = new ExerciseModule(supabase, controller, info, this.event);
|
|
44
|
-
this.storage = new StorageModule(
|
|
45
|
-
controller.onUpdate((updatedInfo) => {
|
|
46
|
-
this.rimoriInfo = updatedInfo;
|
|
47
|
-
});
|
|
42
|
+
this.storage = new StorageModule(controller);
|
|
48
43
|
//only init logger in workers and on main plugin pages
|
|
49
44
|
if (this.plugin.applicationMode !== 'sidebar') {
|
|
50
45
|
Logger.getInstance(this);
|
|
@@ -58,11 +53,13 @@ export class RimoriClient {
|
|
|
58
53
|
static createWithInfo(info) {
|
|
59
54
|
const eventBus = EventBusHandler.create('Plugin EventBus ' + info.pluginId);
|
|
60
55
|
const controller = new RimoriCommunicationHandler(info.pluginId, true);
|
|
56
|
+
controller.handleRimoriInfoUpdate(info);
|
|
61
57
|
const supabase = new PostgrestClient(`${info.url}/rest/v1`, {
|
|
62
58
|
schema: info.dbSchema,
|
|
63
59
|
headers: {
|
|
64
60
|
apikey: info.key,
|
|
65
61
|
Authorization: `Bearer ${info.token}`,
|
|
62
|
+
'plugin-id': info.pluginId,
|
|
66
63
|
},
|
|
67
64
|
});
|
|
68
65
|
const client = new RimoriClient(controller, supabase, info, eventBus);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Language } from './PluginModule';
|
|
2
2
|
import { Tool } from '../../fromRimori/PluginTypes';
|
|
3
|
+
import { RimoriCommunicationHandler } from '../CommunicationHandler';
|
|
3
4
|
export type OnStreamedObjectResult<T = any> = (result: T, isLoading: boolean) => void;
|
|
4
5
|
type PrimitiveType = 'string' | 'number' | 'boolean';
|
|
5
6
|
type ObjectToolParameterType = PrimitiveType | {
|
|
@@ -40,12 +41,10 @@ export type OnLLMResponse = (id: string, response: string, finished: boolean, to
|
|
|
40
41
|
* Provides access to text generation, voice synthesis, and object generation.
|
|
41
42
|
*/
|
|
42
43
|
export declare class AIModule {
|
|
43
|
-
private
|
|
44
|
-
private backendUrl;
|
|
45
|
-
private pluginId;
|
|
44
|
+
private controller;
|
|
46
45
|
private sessionTokenId;
|
|
47
46
|
private onRateLimitedCb?;
|
|
48
|
-
constructor(
|
|
47
|
+
constructor(controller: RimoriCommunicationHandler);
|
|
49
48
|
/**
|
|
50
49
|
* Resolves a prompt name following the event naming convention:
|
|
51
50
|
* - 2-segment names (e.g. 'storytelling.story') get prefixed with pluginId → '<pluginId>.storytelling.story'
|
|
@@ -60,11 +59,6 @@ export declare class AIModule {
|
|
|
60
59
|
set: (id: string) => void;
|
|
61
60
|
/** Clears the stored session token. */
|
|
62
61
|
clear: () => void;
|
|
63
|
-
/**
|
|
64
|
-
* Ensures a session token exists, creating one from the backend if needed.
|
|
65
|
-
* Mirrors the lazy-issuance pattern used by the AI/LLM endpoint.
|
|
66
|
-
*/
|
|
67
|
-
ensure: () => Promise<void>;
|
|
68
62
|
};
|
|
69
63
|
/** Registers a callback invoked whenever a 429 rate-limit response is received. */
|
|
70
64
|
setOnRateLimited(cb: (exercisesRemaining: number) => void): void;
|
|
@@ -109,6 +103,11 @@ export declare class AIModule {
|
|
|
109
103
|
* @param language Optional language for the voice.
|
|
110
104
|
* @param cache Whether to cache the result (default: false).
|
|
111
105
|
* @returns The generated audio as a Blob.
|
|
106
|
+
*
|
|
107
|
+
* **Empty input:** If `text` is empty or whitespace-only, no network request is
|
|
108
|
+
* made and an empty `Blob` is returned immediately. This prevents a 400 error
|
|
109
|
+
* from the TTS backend while keeping the caller's workflow intact.
|
|
110
|
+
* A warning is logged to the console in this case.
|
|
112
111
|
*/
|
|
113
112
|
getVoice(text: string, voice?: string, speed?: number, language?: string, cache?: boolean, instructions?: string): Promise<Blob>;
|
|
114
113
|
/**
|
|
@@ -12,7 +12,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
* Provides access to text generation, voice synthesis, and object generation.
|
|
13
13
|
*/
|
|
14
14
|
export class AIModule {
|
|
15
|
-
constructor(
|
|
15
|
+
constructor(controller) {
|
|
16
16
|
this.sessionTokenId = null;
|
|
17
17
|
/** Exercise session management. */
|
|
18
18
|
this.session = {
|
|
@@ -26,34 +26,8 @@ export class AIModule {
|
|
|
26
26
|
clear: () => {
|
|
27
27
|
this.sessionTokenId = null;
|
|
28
28
|
},
|
|
29
|
-
/**
|
|
30
|
-
* Ensures a session token exists, creating one from the backend if needed.
|
|
31
|
-
* Mirrors the lazy-issuance pattern used by the AI/LLM endpoint.
|
|
32
|
-
*/
|
|
33
|
-
ensure: () => __awaiter(this, void 0, void 0, function* () {
|
|
34
|
-
var _a, _b, _c;
|
|
35
|
-
if (this.sessionTokenId)
|
|
36
|
-
return;
|
|
37
|
-
const response = yield fetch(`${this.backendUrl}/ai/session`, {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers: { Authorization: `Bearer ${this.getToken()}` },
|
|
40
|
-
});
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
if (response.status === 429) {
|
|
43
|
-
const body = yield response.json().catch(() => ({}));
|
|
44
|
-
const remaining = (_a = body.exercises_remaining) !== null && _a !== void 0 ? _a : 0;
|
|
45
|
-
(_b = this.onRateLimitedCb) === null || _b === void 0 ? void 0 : _b.call(this, remaining);
|
|
46
|
-
throw new Error(`Rate limit exceeded: ${(_c = body.error) !== null && _c !== void 0 ? _c : 'Daily exercise limit reached'}. exercises_remaining: ${remaining}`);
|
|
47
|
-
}
|
|
48
|
-
throw new Error(`Failed to create session: ${response.status} ${response.statusText}`);
|
|
49
|
-
}
|
|
50
|
-
const { session_token_id } = yield response.json();
|
|
51
|
-
this.sessionTokenId = session_token_id;
|
|
52
|
-
}),
|
|
53
29
|
};
|
|
54
|
-
this.
|
|
55
|
-
this.getToken = getToken;
|
|
56
|
-
this.pluginId = pluginId;
|
|
30
|
+
this.controller = controller;
|
|
57
31
|
}
|
|
58
32
|
/**
|
|
59
33
|
* Resolves a prompt name following the event naming convention:
|
|
@@ -64,8 +38,8 @@ export class AIModule {
|
|
|
64
38
|
if (name.startsWith('global.'))
|
|
65
39
|
return name;
|
|
66
40
|
const segments = name.split('.');
|
|
67
|
-
if (segments.length === 2 && this.pluginId) {
|
|
68
|
-
return `${this.pluginId}.${name}`;
|
|
41
|
+
if (segments.length === 2 && this.controller.pluginId) {
|
|
42
|
+
return `${this.controller.pluginId}.${name}`;
|
|
69
43
|
}
|
|
70
44
|
return name;
|
|
71
45
|
}
|
|
@@ -128,17 +102,21 @@ export class AIModule {
|
|
|
128
102
|
* @param language Optional language for the voice.
|
|
129
103
|
* @param cache Whether to cache the result (default: false).
|
|
130
104
|
* @returns The generated audio as a Blob.
|
|
105
|
+
*
|
|
106
|
+
* **Empty input:** If `text` is empty or whitespace-only, no network request is
|
|
107
|
+
* made and an empty `Blob` is returned immediately. This prevents a 400 error
|
|
108
|
+
* from the TTS backend while keeping the caller's workflow intact.
|
|
109
|
+
* A warning is logged to the console in this case.
|
|
131
110
|
*/
|
|
132
111
|
getVoice(text_1) {
|
|
133
112
|
return __awaiter(this, arguments, void 0, function* (text, voice = 'alloy', speed = 1, language, cache = false, instructions) {
|
|
134
113
|
var _a;
|
|
135
|
-
|
|
136
|
-
|
|
114
|
+
if (!text.trim().length) {
|
|
115
|
+
console.warn('[rimori-client] getVoice called with empty text — skipping TTS request and returning empty Blob.');
|
|
116
|
+
return new Blob([], { type: 'audio/mpeg' });
|
|
117
|
+
}
|
|
118
|
+
return yield this.controller.fetchBackend('/voice/tts', {
|
|
137
119
|
method: 'POST',
|
|
138
|
-
headers: {
|
|
139
|
-
'Content-Type': 'application/json',
|
|
140
|
-
Authorization: `Bearer ${this.getToken()}`,
|
|
141
|
-
},
|
|
142
120
|
body: JSON.stringify({
|
|
143
121
|
input: text,
|
|
144
122
|
voice,
|
|
@@ -159,7 +137,6 @@ export class AIModule {
|
|
|
159
137
|
*/
|
|
160
138
|
getTextFromVoice(file, language) {
|
|
161
139
|
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
-
yield this.session.ensure();
|
|
163
140
|
const formData = new FormData();
|
|
164
141
|
formData.append('file', file);
|
|
165
142
|
if (language) {
|
|
@@ -168,9 +145,8 @@ export class AIModule {
|
|
|
168
145
|
if (this.sessionTokenId) {
|
|
169
146
|
formData.append('session_token_id', this.sessionTokenId);
|
|
170
147
|
}
|
|
171
|
-
return yield
|
|
148
|
+
return yield this.controller.fetchBackend('/voice/stt', {
|
|
172
149
|
method: 'POST',
|
|
173
|
-
headers: { Authorization: `Bearer ${this.getToken()}` },
|
|
174
150
|
body: formData,
|
|
175
151
|
})
|
|
176
152
|
.then((r) => r.json())
|
|
@@ -236,10 +212,9 @@ export class AIModule {
|
|
|
236
212
|
if (prompt) {
|
|
237
213
|
payload.prompt = { name: this.resolvePromptName(prompt), variables: variables !== null && variables !== void 0 ? variables : {} };
|
|
238
214
|
}
|
|
239
|
-
const response = yield
|
|
240
|
-
body: JSON.stringify(payload),
|
|
215
|
+
const response = yield this.controller.fetchBackend('/ai/llm', {
|
|
241
216
|
method: 'POST',
|
|
242
|
-
|
|
217
|
+
body: JSON.stringify(payload),
|
|
243
218
|
});
|
|
244
219
|
if (!response.ok) {
|
|
245
220
|
if (response.status === 429) {
|
|
@@ -361,13 +336,9 @@ export class AIModule {
|
|
|
361
336
|
}
|
|
362
337
|
sendToolResult(toolCallId, result) {
|
|
363
338
|
return __awaiter(this, void 0, void 0, function* () {
|
|
364
|
-
yield
|
|
339
|
+
yield this.controller.fetchBackend('/ai/llm/tool_result', {
|
|
365
340
|
method: 'POST',
|
|
366
|
-
body: JSON.stringify({
|
|
367
|
-
toolCallId,
|
|
368
|
-
result: result !== null && result !== void 0 ? result : '[DONE]',
|
|
369
|
-
}),
|
|
370
|
-
headers: { Authorization: `Bearer ${this.getToken()} `, 'Content-Type': 'application/json' },
|
|
341
|
+
body: JSON.stringify({ toolCallId, result: result !== null && result !== void 0 ? result : '[DONE]' }),
|
|
371
342
|
});
|
|
372
343
|
});
|
|
373
344
|
}
|
|
@@ -42,7 +42,7 @@ export type VectorSearchResult<T = Record<string, unknown>> = Array<T & {
|
|
|
42
42
|
}>;
|
|
43
43
|
export declare class DbModule {
|
|
44
44
|
private supabase;
|
|
45
|
-
private
|
|
45
|
+
private communicationHandler;
|
|
46
46
|
tablePrefix: string;
|
|
47
47
|
schema: string;
|
|
48
48
|
constructor(supabase: SupabaseClient, communicationHandler: RimoriCommunicationHandler, info: RimoriInfo);
|
|
@@ -10,11 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
export class DbModule {
|
|
11
11
|
constructor(supabase, communicationHandler, info) {
|
|
12
12
|
this.supabase = supabase;
|
|
13
|
-
this.
|
|
13
|
+
this.communicationHandler = communicationHandler;
|
|
14
14
|
this.tablePrefix = info.tablePrefix;
|
|
15
15
|
this.schema = info.dbSchema;
|
|
16
16
|
communicationHandler.onUpdate((updatedInfo) => {
|
|
17
|
-
this.rimoriInfo = updatedInfo;
|
|
18
17
|
this.tablePrefix = updatedInfo.tablePrefix;
|
|
19
18
|
this.schema = updatedInfo.dbSchema;
|
|
20
19
|
});
|
|
@@ -37,7 +36,7 @@ export class DbModule {
|
|
|
37
36
|
if (relation.startsWith('global_')) {
|
|
38
37
|
return this.supabase.schema('public').from(tableName);
|
|
39
38
|
}
|
|
40
|
-
return this.supabase.schema(this.
|
|
39
|
+
return this.supabase.schema(this.schema).from(tableName);
|
|
41
40
|
}
|
|
42
41
|
/**
|
|
43
42
|
* Get the table name for a given plugin table.
|
|
@@ -67,15 +66,11 @@ export class DbModule {
|
|
|
67
66
|
setPublicity(table, entryId, publicity) {
|
|
68
67
|
return __awaiter(this, void 0, void 0, function* () {
|
|
69
68
|
const tableName = this.getTableName(table);
|
|
70
|
-
yield
|
|
69
|
+
yield this.communicationHandler.fetchBackend('/db-entry/publicity', {
|
|
71
70
|
method: 'POST',
|
|
72
|
-
headers: {
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
Authorization: `Bearer ${this.rimoriInfo.token}`,
|
|
75
|
-
},
|
|
76
71
|
body: JSON.stringify({
|
|
77
72
|
table_name: tableName,
|
|
78
|
-
schema: this.
|
|
73
|
+
schema: this.schema,
|
|
79
74
|
entry_id: entryId,
|
|
80
75
|
publicity,
|
|
81
76
|
}),
|
|
@@ -90,12 +85,8 @@ export class DbModule {
|
|
|
90
85
|
*/
|
|
91
86
|
vectorSearch(params) {
|
|
92
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
-
const response = yield
|
|
88
|
+
const response = yield this.communicationHandler.fetchBackend('/plugin-search/vector-search', {
|
|
94
89
|
method: 'POST',
|
|
95
|
-
headers: {
|
|
96
|
-
'Content-Type': 'application/json',
|
|
97
|
-
Authorization: `Bearer ${this.rimoriInfo.token}`,
|
|
98
|
-
},
|
|
99
90
|
body: JSON.stringify(params),
|
|
100
91
|
});
|
|
101
92
|
if (!response.ok) {
|
|
@@ -10,10 +10,8 @@ export declare class EventModule {
|
|
|
10
10
|
private pluginId;
|
|
11
11
|
private accomplishmentController;
|
|
12
12
|
private aiModule;
|
|
13
|
-
private backendUrl;
|
|
14
|
-
private getToken;
|
|
15
13
|
private eventBus;
|
|
16
|
-
constructor(pluginId: string,
|
|
14
|
+
constructor(pluginId: string, aiModule: AIModule, eventBus?: EventBusHandler);
|
|
17
15
|
getGlobalEventTopic(preliminaryTopic: string): string;
|
|
18
16
|
/**
|
|
19
17
|
* Emit an event to Rimori or a plugin.
|
|
@@ -14,13 +14,23 @@ import { EventBus } from '../../fromRimori/EventBus';
|
|
|
14
14
|
* Provides methods for emitting, listening to, and responding to events.
|
|
15
15
|
*/
|
|
16
16
|
export class EventModule {
|
|
17
|
-
constructor(pluginId,
|
|
17
|
+
constructor(pluginId, aiModule, eventBus) {
|
|
18
18
|
this.pluginId = pluginId;
|
|
19
|
-
this.backendUrl = backendUrl;
|
|
20
|
-
this.getToken = getToken;
|
|
21
19
|
this.aiModule = aiModule;
|
|
22
20
|
this.eventBus = eventBus !== null && eventBus !== void 0 ? eventBus : EventBus;
|
|
23
21
|
this.accomplishmentController = new AccomplishmentController(pluginId, this.eventBus);
|
|
22
|
+
// Listen for session token broadcasts from rimori-main (ExerciseSessionManager).
|
|
23
|
+
// When an exercise starts: adopt the exercise token unconditionally.
|
|
24
|
+
// When an exercise ends (session_token: null): clear the current token.
|
|
25
|
+
// This runs on the raw eventBus to bypass the per-plugin token-gating in on().
|
|
26
|
+
this.eventBus.on(['global.session.triggerUpdate'], (event) => {
|
|
27
|
+
if (event.data.session_token) {
|
|
28
|
+
this.aiModule.session.set(event.data.session_token);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.aiModule.session.clear();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
24
34
|
}
|
|
25
35
|
getGlobalEventTopic(preliminaryTopic) {
|
|
26
36
|
var _a;
|
|
@@ -67,7 +77,6 @@ export class EventModule {
|
|
|
67
77
|
return __awaiter(this, void 0, void 0, function* () {
|
|
68
78
|
var _a;
|
|
69
79
|
const globalTopic = this.getGlobalEventTopic(topic);
|
|
70
|
-
yield this.aiModule.session.ensure();
|
|
71
80
|
const sessionToken = (_a = this.aiModule.session.get()) !== null && _a !== void 0 ? _a : undefined;
|
|
72
81
|
return this.eventBus.request(this.pluginId, globalTopic, data, sessionToken);
|
|
73
82
|
});
|
|
@@ -133,21 +142,6 @@ export class EventModule {
|
|
|
133
142
|
*/
|
|
134
143
|
emitAccomplishment(payload) {
|
|
135
144
|
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
-
if (payload.type === 'macro') {
|
|
137
|
-
const sessionId = this.aiModule.session.get();
|
|
138
|
-
if (sessionId) {
|
|
139
|
-
try {
|
|
140
|
-
yield fetch(`${this.backendUrl}/ai/session/${sessionId}/complete`, {
|
|
141
|
-
method: 'PATCH',
|
|
142
|
-
headers: { Authorization: `Bearer ${this.getToken()}` },
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch (_a) {
|
|
146
|
-
// non-fatal — session will expire naturally
|
|
147
|
-
}
|
|
148
|
-
this.aiModule.session.clear();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
145
|
this.accomplishmentController.emitAccomplishment(payload);
|
|
152
146
|
});
|
|
153
147
|
}
|
|
@@ -235,6 +229,9 @@ export class EventModule {
|
|
|
235
229
|
});
|
|
236
230
|
console.log('[EventModule] onSidePanelAction: emitting action.requestSidebar for', this.pluginId);
|
|
237
231
|
this.emit('action.requestSidebar');
|
|
232
|
+
// Bridge is connected at this point — request current session token in case
|
|
233
|
+
// an exercise was already active before this sidebar plugin mounted.
|
|
234
|
+
this.eventBus.emit(this.pluginId, 'global.session.requestCurrent', {});
|
|
238
235
|
return listener;
|
|
239
236
|
}
|
|
240
237
|
}
|
|
@@ -35,9 +35,7 @@ export declare class ExerciseModule {
|
|
|
35
35
|
private supabase;
|
|
36
36
|
private communicationHandler;
|
|
37
37
|
private eventModule;
|
|
38
|
-
|
|
39
|
-
private token;
|
|
40
|
-
constructor(supabase: SupabaseClient, communicationHandler: RimoriCommunicationHandler, info: RimoriInfo, eventModule: EventModule);
|
|
38
|
+
constructor(supabase: SupabaseClient, communicationHandler: RimoriCommunicationHandler, _info: RimoriInfo, eventModule: EventModule);
|
|
41
39
|
/**
|
|
42
40
|
* Fetches weekly exercises from the weekly_exercises view.
|
|
43
41
|
* Shows exercises for the current week that haven't expired.
|
|
@@ -52,6 +50,22 @@ export declare class ExerciseModule {
|
|
|
52
50
|
* @returns Created exercise objects.
|
|
53
51
|
*/
|
|
54
52
|
add(params: CreateExerciseParams | CreateExerciseParams[]): Promise<Exercise[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Requests a new exercise session token from rimori-main.
|
|
55
|
+
* Use this for self-initiated exercises (user navigated to plugin via navbar and clicked Start).
|
|
56
|
+
* For dashboard-triggered exercises (onMainPanelAction), the token is provided automatically.
|
|
57
|
+
*
|
|
58
|
+
* Emits `global.exercise.triggerStart` and waits for rimori-main to respond with the
|
|
59
|
+
* session token via `global.session.triggerUpdate`. The token is then automatically
|
|
60
|
+
* available for AI calls.
|
|
61
|
+
*
|
|
62
|
+
* @param params.actionKey The action key identifying this exercise type.
|
|
63
|
+
* @param params.knowledgeId Optional knowledge ID for tracking what was studied.
|
|
64
|
+
*/
|
|
65
|
+
start(params: {
|
|
66
|
+
actionKey: string;
|
|
67
|
+
knowledgeId?: string;
|
|
68
|
+
}): Promise<void>;
|
|
55
69
|
/**
|
|
56
70
|
* Deletes an exercise via the backend API.
|
|
57
71
|
* @param id The exercise ID to delete.
|
|
@@ -12,15 +12,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
* Provides access to weekly exercises and exercise management.
|
|
13
13
|
*/
|
|
14
14
|
export class ExerciseModule {
|
|
15
|
-
constructor(supabase, communicationHandler,
|
|
15
|
+
constructor(supabase, communicationHandler, _info, eventModule) {
|
|
16
16
|
this.supabase = supabase;
|
|
17
17
|
this.communicationHandler = communicationHandler;
|
|
18
18
|
this.eventModule = eventModule;
|
|
19
|
-
this.token = info.token;
|
|
20
|
-
this.backendUrl = info.backendUrl;
|
|
21
|
-
this.communicationHandler.onUpdate((updatedInfo) => {
|
|
22
|
-
this.token = updatedInfo.token;
|
|
23
|
-
});
|
|
24
19
|
}
|
|
25
20
|
/**
|
|
26
21
|
* Fetches weekly exercises from the weekly_exercises view.
|
|
@@ -46,12 +41,8 @@ export class ExerciseModule {
|
|
|
46
41
|
add(params) {
|
|
47
42
|
return __awaiter(this, void 0, void 0, function* () {
|
|
48
43
|
const exercises = Array.isArray(params) ? params : [params];
|
|
49
|
-
const response = yield
|
|
44
|
+
const response = yield this.communicationHandler.fetchBackend('/exercises', {
|
|
50
45
|
method: 'POST',
|
|
51
|
-
headers: {
|
|
52
|
-
'Content-Type': 'application/json',
|
|
53
|
-
Authorization: `Bearer ${this.token}`,
|
|
54
|
-
},
|
|
55
46
|
body: JSON.stringify({ exercises }),
|
|
56
47
|
});
|
|
57
48
|
if (!response.ok) {
|
|
@@ -63,6 +54,36 @@ export class ExerciseModule {
|
|
|
63
54
|
return data;
|
|
64
55
|
});
|
|
65
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Requests a new exercise session token from rimori-main.
|
|
59
|
+
* Use this for self-initiated exercises (user navigated to plugin via navbar and clicked Start).
|
|
60
|
+
* For dashboard-triggered exercises (onMainPanelAction), the token is provided automatically.
|
|
61
|
+
*
|
|
62
|
+
* Emits `global.exercise.triggerStart` and waits for rimori-main to respond with the
|
|
63
|
+
* session token via `global.session.triggerUpdate`. The token is then automatically
|
|
64
|
+
* available for AI calls.
|
|
65
|
+
*
|
|
66
|
+
* @param params.actionKey The action key identifying this exercise type.
|
|
67
|
+
* @param params.knowledgeId Optional knowledge ID for tracking what was studied.
|
|
68
|
+
*/
|
|
69
|
+
start(params) {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
listener.off();
|
|
74
|
+
reject(new Error('Exercise start timed out: rimori-main did not respond within 5s'));
|
|
75
|
+
}, 5000);
|
|
76
|
+
const listener = this.eventModule.on('global.session.triggerUpdate', ({ data }) => {
|
|
77
|
+
if (data.session_token) {
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
listener.off();
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
this.eventModule.emit('global.exercise.triggerStart', params);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
66
87
|
/**
|
|
67
88
|
* Deletes an exercise via the backend API.
|
|
68
89
|
* @param id The exercise ID to delete.
|
|
@@ -70,11 +91,8 @@ export class ExerciseModule {
|
|
|
70
91
|
*/
|
|
71
92
|
delete(id) {
|
|
72
93
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
const response = yield
|
|
94
|
+
const response = yield this.communicationHandler.fetchBackend(`/exercises/${id}`, {
|
|
74
95
|
method: 'DELETE',
|
|
75
|
-
headers: {
|
|
76
|
-
Authorization: `Bearer ${this.token}`,
|
|
77
|
-
},
|
|
78
96
|
});
|
|
79
97
|
if (!response.ok) {
|
|
80
98
|
const errorText = yield response.text();
|
|
@@ -34,7 +34,6 @@ export class SharedContentController {
|
|
|
34
34
|
// Generate new content via backend endpoint
|
|
35
35
|
const response = yield this.rimoriClient.runtime.fetchBackend('/shared-content/generate', {
|
|
36
36
|
method: 'POST',
|
|
37
|
-
headers: { 'Content-Type': 'application/json' },
|
|
38
37
|
body: JSON.stringify({
|
|
39
38
|
tableName: params.table,
|
|
40
39
|
skillType: params.skillType,
|
|
@@ -67,7 +66,6 @@ export class SharedContentController {
|
|
|
67
66
|
return __awaiter(this, arguments, void 0, function* (tableName, topic, limit = 10) {
|
|
68
67
|
const response = yield this.rimoriClient.runtime.fetchBackend('/shared-content/get-by-topic', {
|
|
69
68
|
method: 'POST',
|
|
70
|
-
headers: { 'Content-Type': 'application/json' },
|
|
71
69
|
body: JSON.stringify({
|
|
72
70
|
tableName,
|
|
73
71
|
limit,
|
|
@@ -285,7 +283,6 @@ export class SharedContentController {
|
|
|
285
283
|
return __awaiter(this, void 0, void 0, function* () {
|
|
286
284
|
const response = yield this.rimoriClient.runtime.fetchBackend('/shared-content/update', {
|
|
287
285
|
method: 'POST',
|
|
288
|
-
headers: { 'Content-Type': 'application/json' },
|
|
289
286
|
body: JSON.stringify({
|
|
290
287
|
tableName,
|
|
291
288
|
contentId,
|
|
@@ -311,7 +308,6 @@ export class SharedContentController {
|
|
|
311
308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
312
309
|
const response = yield this.rimoriClient.runtime.fetchBackend('/shared-content/validate', {
|
|
313
310
|
method: 'POST',
|
|
314
|
-
headers: { 'Content-Type': 'application/json' },
|
|
315
311
|
body: JSON.stringify({
|
|
316
312
|
tableName,
|
|
317
313
|
contentId,
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* confirms images found in content, and deletes orphaned ones. No plugin-side
|
|
9
9
|
* confirm or delete calls are needed.
|
|
10
10
|
*/
|
|
11
|
+
import { RimoriCommunicationHandler } from '../CommunicationHandler';
|
|
11
12
|
export declare class StorageModule {
|
|
12
|
-
private readonly
|
|
13
|
-
|
|
14
|
-
constructor(backendUrl: string, getToken: () => string);
|
|
13
|
+
private readonly controller;
|
|
14
|
+
constructor(controller: RimoriCommunicationHandler);
|
|
15
15
|
/**
|
|
16
16
|
* Upload a PNG image blob to Supabase storage via the backend.
|
|
17
17
|
*
|
|
@@ -7,20 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
11
|
-
* Storage module for plugin image operations.
|
|
12
|
-
*
|
|
13
|
-
* Handles uploading images to Supabase storage via the backend.
|
|
14
|
-
*
|
|
15
|
-
* Images are tracked automatically: the backend cron scans markdown columns
|
|
16
|
-
* (declared via `type: 'markdown'` in db.config.ts) every 30 minutes,
|
|
17
|
-
* confirms images found in content, and deletes orphaned ones. No plugin-side
|
|
18
|
-
* confirm or delete calls are needed.
|
|
19
|
-
*/
|
|
20
10
|
export class StorageModule {
|
|
21
|
-
constructor(
|
|
22
|
-
this.
|
|
23
|
-
this.getToken = getToken;
|
|
11
|
+
constructor(controller) {
|
|
12
|
+
this.controller = controller;
|
|
24
13
|
}
|
|
25
14
|
/**
|
|
26
15
|
* Upload a PNG image blob to Supabase storage via the backend.
|
|
@@ -37,9 +26,8 @@ export class StorageModule {
|
|
|
37
26
|
const formData = new FormData();
|
|
38
27
|
formData.append('file', pngBlob, 'image.png');
|
|
39
28
|
try {
|
|
40
|
-
const response = yield
|
|
29
|
+
const response = yield this.controller.fetchBackend('/plugin-images/upload', {
|
|
41
30
|
method: 'POST',
|
|
42
|
-
headers: { Authorization: `Bearer ${this.getToken()}` },
|
|
43
31
|
body: formData,
|
|
44
32
|
});
|
|
45
33
|
if (!response.ok) {
|