@rimori/client 2.5.28 → 2.5.29-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -140,7 +140,7 @@ Helpers:
140
140
  The `client.ai` controller surfaces AI capabilities:
141
141
 
142
142
  - `getText(messages, tools?)` – chat completion (string result).
143
- - `getSteamedText(messages, onMessage, tools?)` – streamed responses.
143
+ - `getStreamedText(messages, onMessage, tools?)` – streamed responses.
144
144
  - `getObject(request)` – structured JSON generation.
145
145
  - `getVoice(text, voice?, speed?, language?)` – text-to-speech (returns `Blob`).
146
146
  - `getTextFromVoice(file)` – speech-to-text transcription.
@@ -48,7 +48,7 @@ export class Translator {
48
48
  },
49
49
  },
50
50
  debug: false,
51
- // showSupportNotice: false, // TODO enable with next version of i18next
51
+ showSupportNotice: false,
52
52
  parseMissingKeyHandler: (key, defaultValue) => {
53
53
  if (!key.trim())
54
54
  return '';
@@ -134,6 +134,7 @@ export class Translator {
134
134
  if (!this.i18n) {
135
135
  throw new Error('Translator is not initialized');
136
136
  }
137
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
137
138
  return this.i18n.t(key, options);
138
139
  }
139
140
  /**
@@ -40,6 +40,10 @@ export interface RimoriInfo {
40
40
  * - 'plugins' for beta and stable release channels
41
41
  */
42
42
  dbSchema: 'plugins' | 'plugins_alpha';
43
+ /**
44
+ * Whether text-to-speech is enabled globally (set in rimori-main navbar).
45
+ */
46
+ ttsEnabled: boolean;
43
47
  }
44
48
  export declare class RimoriCommunicationHandler {
45
49
  private port;
@@ -13,6 +13,8 @@ export declare class ChunkedAudioPlayer {
13
13
  private startedPlaying;
14
14
  private onEndOfSpeech;
15
15
  private readonly backgroundNoiseLevel;
16
+ private currentSource;
17
+ private stopped;
16
18
  constructor();
17
19
  private init;
18
20
  setOnLoudnessChange(callback: (value: number) => void): void;
@@ -20,6 +20,8 @@ export class ChunkedAudioPlayer {
20
20
  this.startedPlaying = false;
21
21
  this.onEndOfSpeech = () => { };
22
22
  this.backgroundNoiseLevel = 30; // Background noise level that should be treated as baseline (0)
23
+ this.currentSource = null;
24
+ this.stopped = false;
23
25
  this.init();
24
26
  }
25
27
  init() {
@@ -37,12 +39,10 @@ export class ChunkedAudioPlayer {
37
39
  }
38
40
  addChunk(chunk, position) {
39
41
  return __awaiter(this, void 0, void 0, function* () {
42
+ if (this.stopped)
43
+ return;
40
44
  console.log('Adding chunk', position, chunk);
41
45
  this.chunkQueue[position] = chunk;
42
- // console.log("received chunk", {
43
- // chunkQueue: this.chunkQueue.length,
44
- // isPlaying: this.isPlaying,
45
- // })
46
46
  if (position === 0 && !this.startedPlaying) {
47
47
  this.startedPlaying = true;
48
48
  this.playChunks();
@@ -50,25 +50,27 @@ export class ChunkedAudioPlayer {
50
50
  });
51
51
  }
52
52
  playChunks() {
53
- // console.log({ isPlaying: this.isPlaying });
54
- if (this.isPlaying)
53
+ if (this.isPlaying || this.stopped)
55
54
  return;
56
55
  if (!this.chunkQueue[this.currentIndex]) {
57
56
  // wait until the correct chunk arrives
58
57
  setTimeout(() => this.playChunks(), 10);
58
+ return;
59
59
  }
60
60
  this.isPlaying = true;
61
61
  this.playChunk(this.chunkQueue[this.currentIndex]).then(() => {
62
62
  this.isPlaying = false;
63
+ if (this.stopped)
64
+ return;
63
65
  this.currentIndex++;
64
66
  if (this.chunkQueue[this.currentIndex]) {
65
67
  this.shouldMonitorLoudness = true;
66
68
  this.playChunks();
67
69
  }
68
70
  else {
69
- // console.log('Playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
70
71
  setTimeout(() => {
71
- // console.log('Check again if really playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
72
+ if (this.stopped)
73
+ return;
72
74
  if (this.chunkQueue.length > this.currentIndex) {
73
75
  this.playChunks();
74
76
  }
@@ -81,13 +83,23 @@ export class ChunkedAudioPlayer {
81
83
  });
82
84
  }
83
85
  stopPlayback() {
84
- // console.log('Stopping playback');
85
- // Implement logic to stop the current playback
86
+ this.stopped = true;
87
+ // Stop the currently playing audio source node
88
+ if (this.currentSource) {
89
+ try {
90
+ this.currentSource.stop();
91
+ }
92
+ catch (_a) {
93
+ // already stopped
94
+ }
95
+ this.currentSource = null;
96
+ }
86
97
  this.isPlaying = false;
87
98
  this.chunkQueue = [];
88
99
  this.startedPlaying = false;
89
100
  this.shouldMonitorLoudness = false;
90
101
  cancelAnimationFrame(this.handle);
102
+ this.loudnessCallback(0);
91
103
  }
92
104
  cleanup() {
93
105
  // Stop playback first
@@ -100,14 +112,17 @@ export class ChunkedAudioPlayer {
100
112
  }
101
113
  }
102
114
  playChunk(chunk) {
103
- // console.log({queue: this.chunkQueue})
104
- if (!chunk) {
115
+ if (!chunk || this.stopped) {
105
116
  return Promise.resolve();
106
117
  }
107
- // console.log('Playing chunk', chunk);
108
118
  return new Promise((resolve) => {
109
119
  const source = this.audioContext.createBufferSource();
120
+ this.currentSource = source;
110
121
  this.audioContext.decodeAudioData(chunk.slice(0)).then((audioBuffer) => {
122
+ if (this.stopped) {
123
+ resolve();
124
+ return;
125
+ }
111
126
  source.buffer = audioBuffer;
112
127
  // Create a GainNode for volume control
113
128
  const gainNode = this.audioContext.createGain();
@@ -117,10 +132,11 @@ export class ChunkedAudioPlayer {
117
132
  gainNode.connect(this.analyser);
118
133
  this.analyser.connect(this.audioContext.destination);
119
134
  source.start(0);
120
- // console.log('Playing chunk', this.currentIndex);
121
135
  gainNode.gain.value = this.volume;
122
136
  source.onended = () => {
123
- // console.log('Chunk ended');
137
+ if (this.currentSource === source) {
138
+ this.currentSource = null;
139
+ }
124
140
  resolve();
125
141
  };
126
142
  // Start monitoring loudness only once
@@ -190,11 +206,10 @@ export class ChunkedAudioPlayer {
190
206
  this.handle = requestAnimationFrame(() => this.monitorLoudness());
191
207
  }
192
208
  reset() {
193
- // console.log('Resetting player');
194
209
  this.stopPlayback();
210
+ this.stopped = false;
195
211
  this.currentIndex = 0;
196
212
  this.shouldMonitorLoudness = true;
197
- //reset to the beginning when the class gets initialized
198
213
  this.isMonitoring = false;
199
214
  this.isPlaying = false;
200
215
  this.init();
@@ -35,24 +35,6 @@ export interface Message {
35
35
  toolCalls?: ToolInvocation[];
36
36
  }
37
37
  export type OnLLMResponse = (id: string, response: string, finished: boolean, toolInvocations?: ToolInvocation[]) => void;
38
- export interface ObjectRequest {
39
- /**
40
- * The tools that the AI can use.
41
- */
42
- tool: AIObjectTool;
43
- /**
44
- * High level instructions for the AI to follow. Behaviour, tone, restrictions, etc.
45
- * Example: "Act like a recipe writer."
46
- */
47
- /** @deprecated Use server-side prompt definitions (prompt + variables) instead of building requests client-side. */
48
- behaviour?: string;
49
- /**
50
- * The specific instruction for the AI to follow.
51
- * Example: "Generate a recipe using chicken, rice and vegetables."
52
- */
53
- /** @deprecated Use server-side prompt definitions (prompt + variables) instead of building requests client-side. */
54
- instructions: string;
55
- }
56
38
  /**
57
39
  * Controller for AI-related operations.
58
40
  * Provides access to text generation, voice synthesis, and object generation.
@@ -88,24 +70,41 @@ export declare class AIModule {
88
70
  setOnRateLimited(cb: (exercisesRemaining: number) => void): void;
89
71
  /**
90
72
  * Generate text from messages using AI.
91
- * @param messages The messages to generate text from.
92
- * @param tools Optional tools to use for generation.
93
- * @param cache Whether to cache the result (default: false).
94
- * @param model The model to use for generation.
73
+ * @param params.messages The messages to generate text from.
74
+ * @param params.tools Optional tools to use for generation.
75
+ * @param params.cache Whether to cache the result (default: false).
76
+ * @param params.model The model to use for generation.
77
+ * @param params.prompt Server-side prompt name (e.g. 'writing.analysis').
78
+ * @param params.variables Variables for the server-side prompt template.
95
79
  * @returns The generated text.
96
80
  */
97
- getText(messages: Message[], tools?: Tool[], cache?: boolean, model?: string): Promise<string>;
81
+ getText(params: {
82
+ messages: Message[];
83
+ tools?: Tool[];
84
+ cache?: boolean;
85
+ model?: string;
86
+ prompt?: string;
87
+ variables?: Record<string, any>;
88
+ }): Promise<string>;
98
89
  /**
99
90
  * Stream text generation from messages using AI.
100
- * @param messages The messages to generate text from.
101
- * @param onMessage Callback for each message chunk.
102
- * @param tools Optional tools to use for generation.
103
- * @param cache Whether to cache the result (default: false).
104
- * @param model The model to use for generation.
91
+ * @param params.messages The messages to generate text from.
92
+ * @param params.onMessage Callback for each message chunk.
93
+ * @param params.tools Optional tools to use for generation.
94
+ * @param params.cache Whether to cache the result (default: false).
95
+ * @param params.model The model to use for generation.
96
+ * @param params.prompt Server-side prompt name (e.g. 'writing.analysis').
97
+ * @param params.variables Variables for the server-side prompt template.
105
98
  */
106
- getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[], cache?: boolean, model?: string,
107
- /** @deprecated Use uuid variable with resolver 'knowledgeEntry' in prompt definitions instead. */
108
- knowledgeId?: string, prompt?: string, variables?: Record<string, any>): Promise<string>;
99
+ getStreamedText(params: {
100
+ messages: Message[];
101
+ onMessage: OnLLMResponse;
102
+ tools?: Tool[];
103
+ cache?: boolean;
104
+ model?: string;
105
+ prompt?: string;
106
+ variables?: Record<string, any>;
107
+ }): Promise<string>;
109
108
  /**
110
109
  * Generate voice audio from text using AI.
111
110
  * @param text The text to convert to voice.
@@ -123,8 +122,6 @@ export declare class AIModule {
123
122
  * @returns The transcribed text.
124
123
  */
125
124
  getTextFromVoice(file: Blob, language?: Language): Promise<string>;
126
- /** @deprecated Used by legacy client-side prompt path. Will be removed once all plugins migrate to server-side prompt definitions. */
127
- private getChatMessage;
128
125
  /**
129
126
  * Generate a structured object from a request using AI.
130
127
  * @param request.cache Whether to cache the result (default: false).
@@ -135,17 +132,9 @@ export declare class AIModule {
135
132
  * @returns The generated object.
136
133
  */
137
134
  getObject<T = any>(params: {
138
- /** @deprecated Use prompt + variables instead of client-side system prompts. */
139
- systemPrompt?: string;
140
- /** @deprecated Use prompt + variables instead. Schema is loaded server-side from the prompt definition. */
141
- responseSchema?: AIObjectTool;
142
- /** @deprecated Use prompt + variables instead of client-side user prompts. */
143
- userPrompt?: string;
144
135
  cache?: boolean;
145
136
  tools?: Tool[];
146
137
  model?: string;
147
- /** @deprecated Use uuid variable with resolver 'knowledgeEntry' in prompt definitions instead. */
148
- knowledgeId?: string;
149
138
  prompt?: string;
150
139
  variables?: Record<string, any>;
151
140
  }): Promise<T>;
@@ -159,18 +148,10 @@ export declare class AIModule {
159
148
  * @param request.variables Variables for the server-side prompt template.
160
149
  */
161
150
  getStreamedObject<T = any>(params: {
162
- /** @deprecated Use prompt + variables instead of client-side system prompts. */
163
- systemPrompt?: string;
164
- /** @deprecated Use prompt + variables instead. Schema is loaded server-side from the prompt definition. */
165
- responseSchema?: AIObjectTool;
166
- /** @deprecated Use prompt + variables instead of client-side user prompts. */
167
- userPrompt?: string;
168
151
  onResult: OnStreamedObjectResult<T>;
169
152
  cache?: boolean;
170
153
  tools?: Tool[];
171
154
  model?: string;
172
- /** @deprecated Use uuid variable with resolver 'knowledgeEntry' in prompt definitions instead. */
173
- knowledgeId?: string;
174
155
  prompt?: string;
175
156
  variables?: Record<string, any>;
176
157
  }): Promise<T>;
@@ -75,42 +75,47 @@ export class AIModule {
75
75
  }
76
76
  /**
77
77
  * Generate text from messages using AI.
78
- * @param messages The messages to generate text from.
79
- * @param tools Optional tools to use for generation.
80
- * @param cache Whether to cache the result (default: false).
81
- * @param model The model to use for generation.
78
+ * @param params.messages The messages to generate text from.
79
+ * @param params.tools Optional tools to use for generation.
80
+ * @param params.cache Whether to cache the result (default: false).
81
+ * @param params.model The model to use for generation.
82
+ * @param params.prompt Server-side prompt name (e.g. 'writing.analysis').
83
+ * @param params.variables Variables for the server-side prompt template.
82
84
  * @returns The generated text.
83
85
  */
84
- getText(messages_1, tools_1) {
85
- return __awaiter(this, arguments, void 0, function* (messages, tools, cache = false, model) {
86
+ getText(params) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ const { messages, tools, cache = false, model, prompt, variables } = params;
86
89
  const { result } = yield this.streamObject({
87
90
  cache,
88
91
  tools,
89
92
  model,
90
93
  messages,
94
+ prompt,
95
+ variables,
91
96
  });
92
97
  return result;
93
98
  });
94
99
  }
95
100
  /**
96
101
  * Stream text generation from messages using AI.
97
- * @param messages The messages to generate text from.
98
- * @param onMessage Callback for each message chunk.
99
- * @param tools Optional tools to use for generation.
100
- * @param cache Whether to cache the result (default: false).
101
- * @param model The model to use for generation.
102
+ * @param params.messages The messages to generate text from.
103
+ * @param params.onMessage Callback for each message chunk.
104
+ * @param params.tools Optional tools to use for generation.
105
+ * @param params.cache Whether to cache the result (default: false).
106
+ * @param params.model The model to use for generation.
107
+ * @param params.prompt Server-side prompt name (e.g. 'writing.analysis').
108
+ * @param params.variables Variables for the server-side prompt template.
102
109
  */
103
- getSteamedText(messages_1, onMessage_1, tools_1) {
104
- return __awaiter(this, arguments, void 0, function* (messages, onMessage, tools, cache = false, model,
105
- /** @deprecated Use uuid variable with resolver 'knowledgeEntry' in prompt definitions instead. */
106
- knowledgeId, prompt, variables) {
110
+ getStreamedText(params) {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ const { messages, onMessage, tools, cache = false, model, prompt, variables, } = params;
107
113
  const messageId = Math.random().toString(36).substring(3);
108
114
  const { result } = yield this.streamObject({
109
115
  cache,
110
116
  tools,
111
117
  model,
112
118
  messages,
113
- knowledgeId,
114
119
  prompt,
115
120
  variables,
116
121
  onResult: ({ result }) => onMessage(messageId, result, false),
@@ -179,14 +184,6 @@ export class AIModule {
179
184
  });
180
185
  });
181
186
  }
182
- /** @deprecated Used by legacy client-side prompt path. Will be removed once all plugins migrate to server-side prompt definitions. */
183
- getChatMessage(systemPrompt, userPrompt) {
184
- const messages = [{ role: 'system', content: systemPrompt }];
185
- if (userPrompt) {
186
- messages.push({ role: 'user', content: userPrompt });
187
- }
188
- return messages;
189
- }
190
187
  /**
191
188
  * Generate a structured object from a request using AI.
192
189
  * @param request.cache Whether to cache the result (default: false).
@@ -198,14 +195,12 @@ export class AIModule {
198
195
  */
199
196
  getObject(params) {
200
197
  return __awaiter(this, void 0, void 0, function* () {
201
- const { systemPrompt, responseSchema, userPrompt, cache = false, tools = [], model = undefined, knowledgeId, prompt, variables, } = params;
198
+ const { cache = false, tools = [], model = undefined, prompt, variables } = params;
202
199
  return yield this.streamObject({
203
- responseSchema,
204
- messages: systemPrompt ? this.getChatMessage(systemPrompt, userPrompt) : [],
200
+ messages: [],
205
201
  cache,
206
202
  tools,
207
203
  model,
208
- knowledgeId,
209
204
  prompt,
210
205
  variables,
211
206
  });
@@ -222,15 +217,13 @@ export class AIModule {
222
217
  */
223
218
  getStreamedObject(params) {
224
219
  return __awaiter(this, void 0, void 0, function* () {
225
- const { systemPrompt, responseSchema, userPrompt, onResult, cache = false, tools = [], model = undefined, knowledgeId, prompt, variables, } = params;
220
+ const { onResult, cache = false, tools = [], model = undefined, prompt, variables } = params;
226
221
  return yield this.streamObject({
227
- responseSchema,
228
- messages: systemPrompt ? this.getChatMessage(systemPrompt, userPrompt) : [],
222
+ messages: [],
229
223
  onResult,
230
224
  cache,
231
225
  tools,
232
226
  model,
233
- knowledgeId,
234
227
  prompt,
235
228
  variables,
236
229
  });
@@ -239,7 +232,7 @@ export class AIModule {
239
232
  streamObject(params) {
240
233
  return __awaiter(this, void 0, void 0, function* () {
241
234
  var _a, _b, _c, _d, _e;
242
- const { messages, responseSchema, onResult = () => null, cache = false, tools = [], model = undefined, knowledgeId, prompt, variables, } = params;
235
+ const { messages, onResult = () => null, cache = false, tools = [], model = undefined, prompt, variables, } = params;
243
236
  const chatMessages = messages.map((message, index) => (Object.assign(Object.assign({}, message), { id: `${index + 1}` })));
244
237
  const payload = {
245
238
  cache,
@@ -247,15 +240,11 @@ export class AIModule {
247
240
  stream: true,
248
241
  messages: chatMessages,
249
242
  model,
250
- knowledge_id: knowledgeId,
251
243
  session_token_id: (_a = this.sessionTokenId) !== null && _a !== void 0 ? _a : undefined,
252
244
  };
253
245
  if (prompt) {
254
246
  payload.prompt = { name: this.resolvePromptName(prompt), variables: variables !== null && variables !== void 0 ? variables : {} };
255
247
  }
256
- if (responseSchema) {
257
- payload.responseSchema = responseSchema;
258
- }
259
248
  const response = yield fetch(`${this.backendUrl}/ai/llm`, {
260
249
  body: JSON.stringify(payload),
261
250
  method: 'POST',
@@ -23,6 +23,11 @@ export declare class PluginModule {
23
23
  releaseChannel: string;
24
24
  applicationMode: ApplicationMode;
25
25
  theme: Theme;
26
+ /**
27
+ * Whether text-to-speech is globally enabled (set in rimori-main navbar).
28
+ * Updated automatically when the user toggles TTS.
29
+ */
30
+ ttsEnabled: boolean;
26
31
  constructor(supabase: SupabaseClient, communicationHandler: RimoriCommunicationHandler, info: RimoriInfo, ai: AIModule);
27
32
  /**
28
33
  * Fetches settings based on guild configuration.
@@ -14,6 +14,12 @@ import { Translator } from '../../controller/TranslationController';
14
14
  */
15
15
  export class PluginModule {
16
16
  constructor(supabase, communicationHandler, info, ai) {
17
+ var _a;
18
+ /**
19
+ * Whether text-to-speech is globally enabled (set in rimori-main navbar).
20
+ * Updated automatically when the user toggles TTS.
21
+ */
22
+ this.ttsEnabled = true;
17
23
  this.rimoriInfo = info;
18
24
  this.supabase = supabase;
19
25
  this.pluginId = info.pluginId;
@@ -21,7 +27,12 @@ export class PluginModule {
21
27
  this.communicationHandler = communicationHandler;
22
28
  const currentPlugin = info.installedPlugins.find((plugin) => plugin.id === info.pluginId);
23
29
  this.translator = new Translator(info.interfaceLanguage, (currentPlugin === null || currentPlugin === void 0 ? void 0 : currentPlugin.endpoint) || '', ai);
24
- this.communicationHandler.onUpdate((updatedInfo) => (this.rimoriInfo = updatedInfo));
30
+ this.ttsEnabled = (_a = info.ttsEnabled) !== null && _a !== void 0 ? _a : true;
31
+ this.communicationHandler.onUpdate((updatedInfo) => {
32
+ var _a;
33
+ this.rimoriInfo = updatedInfo;
34
+ this.ttsEnabled = (_a = updatedInfo.ttsEnabled) !== null && _a !== void 0 ? _a : true;
35
+ });
25
36
  this.applicationMode = this.communicationHandler.getQueryParam('applicationMode');
26
37
  this.theme = this.communicationHandler.getQueryParam('rm_theme') || 'light';
27
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/client",
3
- "version": "2.5.28",
3
+ "version": "2.5.29-next.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "format": "prettier --write ."
37
37
  },
38
38
  "dependencies": {
39
- "@supabase/postgrest-js": "^2.98.0",
39
+ "@supabase/postgrest-js": "^2.100.1",
40
40
  "dotenv": "^16.5.0",
41
- "i18next": "^25.8.15"
41
+ "i18next": "^25.10.10"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/js": "^9.37.0",