@rimori/client 1.2.0 → 1.3.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.
Files changed (75) hide show
  1. package/README.md +61 -18
  2. package/dist/cli/scripts/init/dev-registration.js +0 -1
  3. package/dist/cli/scripts/init/main.d.ts +1 -1
  4. package/dist/cli/scripts/init/main.js +1 -0
  5. package/dist/components/LoggerExample.d.ts +6 -0
  6. package/dist/components/LoggerExample.js +79 -0
  7. package/dist/components/ai/Assistant.js +2 -2
  8. package/dist/components/ai/Avatar.js +2 -2
  9. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -32
  10. package/dist/components/audio/Playbutton.js +2 -2
  11. package/dist/components/components/ContextMenu.js +48 -9
  12. package/dist/core/controller/AIController.js +202 -69
  13. package/dist/core/controller/AudioController.d.ts +0 -0
  14. package/dist/core/controller/AudioController.js +1 -0
  15. package/dist/core/controller/ObjectController.d.ts +2 -2
  16. package/dist/core/controller/ObjectController.js +8 -8
  17. package/dist/core/controller/SettingsController.d.ts +16 -0
  18. package/dist/core/controller/SharedContentController.d.ts +30 -2
  19. package/dist/core/controller/SharedContentController.js +74 -23
  20. package/dist/core/controller/VoiceController.d.ts +2 -3
  21. package/dist/core/controller/VoiceController.js +11 -4
  22. package/dist/core/core.d.ts +1 -0
  23. package/dist/fromRimori/EventBus.js +1 -1
  24. package/dist/fromRimori/PluginTypes.d.ts +7 -4
  25. package/dist/hooks/UseChatHook.js +6 -4
  26. package/dist/hooks/UseLogger.d.ts +30 -0
  27. package/dist/hooks/UseLogger.js +122 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/plugin/AudioController.d.ts +37 -0
  31. package/dist/plugin/AudioController.js +68 -0
  32. package/dist/plugin/Logger.d.ts +68 -0
  33. package/dist/plugin/Logger.js +256 -0
  34. package/dist/plugin/LoggerExample.d.ts +16 -0
  35. package/dist/plugin/LoggerExample.js +140 -0
  36. package/dist/plugin/PluginController.d.ts +15 -3
  37. package/dist/plugin/PluginController.js +162 -39
  38. package/dist/plugin/RimoriClient.d.ts +55 -13
  39. package/dist/plugin/RimoriClient.js +60 -23
  40. package/dist/plugin/StandaloneClient.d.ts +1 -0
  41. package/dist/plugin/StandaloneClient.js +16 -5
  42. package/dist/plugin/ThemeSetter.d.ts +2 -2
  43. package/dist/plugin/ThemeSetter.js +8 -5
  44. package/dist/providers/PluginProvider.d.ts +1 -1
  45. package/dist/providers/PluginProvider.js +36 -10
  46. package/dist/utils/audioFormats.d.ts +26 -0
  47. package/dist/utils/audioFormats.js +67 -0
  48. package/dist/worker/WorkerSetup.d.ts +3 -2
  49. package/dist/worker/WorkerSetup.js +22 -67
  50. package/package.json +2 -1
  51. package/src/cli/scripts/init/dev-registration.ts +0 -1
  52. package/src/cli/scripts/init/main.ts +1 -0
  53. package/src/components/ai/Assistant.tsx +2 -2
  54. package/src/components/ai/Avatar.tsx +2 -2
  55. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +39 -32
  56. package/src/components/audio/Playbutton.tsx +2 -2
  57. package/src/components/components/ContextMenu.tsx +53 -9
  58. package/src/core/controller/AIController.ts +236 -75
  59. package/src/core/controller/ObjectController.ts +8 -8
  60. package/src/core/controller/SettingsController.ts +16 -0
  61. package/src/core/controller/SharedContentController.ts +87 -25
  62. package/src/core/controller/VoiceController.ts +24 -19
  63. package/src/core/core.ts +1 -0
  64. package/src/fromRimori/EventBus.ts +1 -1
  65. package/src/fromRimori/PluginTypes.ts +6 -4
  66. package/src/hooks/UseChatHook.ts +6 -4
  67. package/src/index.ts +1 -0
  68. package/src/plugin/AudioController.ts +58 -0
  69. package/src/plugin/Logger.ts +324 -0
  70. package/src/plugin/PluginController.ts +171 -43
  71. package/src/plugin/RimoriClient.ts +95 -30
  72. package/src/plugin/StandaloneClient.ts +22 -6
  73. package/src/plugin/ThemeSetter.ts +8 -5
  74. package/src/providers/PluginProvider.tsx +40 -10
  75. package/src/worker/WorkerSetup.ts +14 -63
package/README.md CHANGED
@@ -163,6 +163,43 @@ If you encounter release issues:
163
163
  3. **Build errors**: Run `yarn build` successfully before releasing
164
164
  4. **Authentication**: Your token may have expired - re-run `rimori-init` if needed
165
165
 
166
+ #### Worker Process Errors
167
+
168
+ If you encounter errors like:
169
+ ```
170
+ ReferenceError: process is not defined
171
+ ```
172
+
173
+ **Root Cause**: Your plugin is importing a library that tries to access `process` or `process.env` in the worker context. Web workers don't have access to Node.js globals like `process`.
174
+
175
+ **Debugging Steps**:
176
+
177
+ 1. **Check your imports**: Look through the files used in the worker which might import libraries that might access `process.env`:
178
+ - React libraries (React Router, React Query, etc.)
179
+ - UI component libraries (Material-UI, Ant Design, etc.)
180
+ - Utility libraries that check for environment variables
181
+
182
+ 2. **Verify Rimori Client imports**: Ensure you're importing from `@rimori/client/core` and not from `@rimori/client`:
183
+ ```typescript
184
+ // ✅ Correct - import from rimori client
185
+ import { RimoriClient } from '@rimori/client/core';
186
+
187
+ // ❌ Incorrect - direct imports that might access process.env directly or through their dependencies
188
+ import { Avatar } from '@rimori/client';
189
+ import { useQuery } from '@tanstack/react-query';
190
+ ```
191
+
192
+ 3. **Check your dependencies**: Look at your `package.json` for libraries that might be bundled into the worker:
193
+ - UI frameworks (React, Vue, etc.)
194
+ - State management libraries
195
+ - Routing libraries
196
+ - Any library that checks `process.env.NODE_ENV`
197
+
198
+
199
+ **Solution**: Use only `@rimori/client/core` exports in your worker code. The Rimori client provides all necessary functionality without requiring Node.js globals.
200
+
201
+ **Note**: The worker bundling order is determined by the build system and cannot be controlled. Libraries that access `process.env` will always cause this error in the worker context.
202
+
166
203
 
167
204
 
168
205
 
@@ -171,13 +208,13 @@ If you encounter release issues:
171
208
 
172
209
  ## Core API - usePlugin Hook
173
210
 
174
- The `usePlugin()` hook is the main interface for accessing Rimori platform features:
211
+ The `useRimori()` hook is the main interface for accessing Rimori platform features:
175
212
 
176
213
  ```typescript
177
- import { usePlugin } from "@rimori/client";
214
+ import { useRimori } from "@rimori/client";
178
215
 
179
216
  const MyComponent = () => {
180
- const client = usePlugin();
217
+ const client = useRimori();
181
218
 
182
219
  // Access all client features
183
220
  const { db, llm, event, community, plugin } = client;
@@ -189,7 +226,7 @@ const MyComponent = () => {
189
226
  ### Plugin Interface
190
227
 
191
228
  ```typescript
192
- const { plugin } = usePlugin();
229
+ const { plugin } = useRimori();
193
230
 
194
231
  // Plugin information and settings
195
232
  plugin.pluginId: string // Current plugin ID
@@ -211,7 +248,7 @@ interface FlashcardSettings {
211
248
  }
212
249
 
213
250
  const FlashcardSettingsComponent = () => {
214
- const { plugin } = usePlugin();
251
+ const { plugin } = useRimori();
215
252
  const [settings, setSettings] = useState<FlashcardSettings>();
216
253
 
217
254
  useEffect(() => {
@@ -280,7 +317,7 @@ const FlashcardSettingsComponent = () => {
280
317
  Access your plugin's dedicated database tables with full TypeScript support:
281
318
 
282
319
  ```typescript
283
- const { db } = usePlugin();
320
+ const { db } = useRimori();
284
321
 
285
322
  // Database interface
286
323
  db.from(tableName) // Query builder for tables/views - supports ALL Supabase operations
@@ -298,6 +335,12 @@ The `db.from()` method provides access to the complete Supabase PostgREST API, s
298
335
 
299
336
  All operations automatically use your plugin's table prefix for security and isolation.
300
337
 
338
+ **Column deprecation and removal (short policy)**
339
+
340
+ - To remove a column, use a two-step process:
341
+ 1. Mark the column with `deprecated: true` in your `rimori/db.config.ts` and release. The backend renames the column to `<name>_old`.
342
+ 2. In a subsequent release, remove the column from the config. The backend drops `<name>_old`.
343
+
301
344
  **Example: CRUD Operations**
302
345
 
303
346
  ```typescript
@@ -310,7 +353,7 @@ interface StudySession {
310
353
  }
311
354
 
312
355
  const StudySessionManager = () => {
313
- const { db } = usePlugin();
356
+ const { db } = useRimori();
314
357
 
315
358
  // Create a new study session
316
359
  const createSession = async (session: Omit<StudySession, 'id'>) => {
@@ -361,7 +404,7 @@ const StudySessionManager = () => {
361
404
 
362
405
  ```typescript
363
406
  const FileManager = () => {
364
- const { db } = usePlugin();
407
+ const { db } = useRimori();
365
408
 
366
409
  const uploadFile = async (file: File) => {
367
410
  const fileName = `uploads/${Date.now()}-${file.name}`;
@@ -392,7 +435,7 @@ const FileManager = () => {
392
435
  Powerful AI/Language Model capabilities built-in:
393
436
 
394
437
  ```typescript
395
- const { llm } = usePlugin();
438
+ const { llm } = useRimori();
396
439
 
397
440
  // Text generation
398
441
  llm.getText(messages: Message[], tools?: Tool[]): Promise<string>
@@ -461,7 +504,7 @@ const ChatAssistant = () => {
461
504
 
462
505
  ```typescript
463
506
  const QuizGenerator = () => {
464
- const { llm } = usePlugin();
507
+ const { llm } = useRimori();
465
508
 
466
509
  const generateQuiz = async (topic: string) => {
467
510
  const quiz = await llm.getObject({
@@ -497,7 +540,7 @@ const QuizGenerator = () => {
497
540
 
498
541
  ```typescript
499
542
  const VoiceAssistant = () => {
500
- const { llm } = usePlugin();
543
+ const { llm } = useRimori();
501
544
 
502
545
  const speakText = async (text: string) => {
503
546
  const audioBlob = await llm.getVoice(text, "alloy", 1, "en");
@@ -520,7 +563,7 @@ const VoiceAssistant = () => {
520
563
  Robust inter-plugin communication and platform integration:
521
564
 
522
565
  ```typescript
523
- const { event } = usePlugin();
566
+ const { event } = useRimori();
524
567
 
525
568
  // Event methods
526
569
  event.emit(topic: string, data?: any, eventId?: number): void
@@ -541,7 +584,7 @@ event.emitSidebarAction(pluginId: string, actionKey: string, text?: string): voi
541
584
 
542
585
  ```typescript
543
586
  const PluginCommunicator = () => {
544
- const { event } = usePlugin();
587
+ const { event } = useRimori();
545
588
 
546
589
  useEffect(() => {
547
590
  // Listen for messages from other plugins
@@ -591,7 +634,7 @@ const PluginCommunicator = () => {
591
634
 
592
635
  ```typescript
593
636
  const AccomplishmentTracker = () => {
594
- const { event } = usePlugin();
637
+ const { event } = useRimori();
595
638
 
596
639
  const trackAccomplishment = () => {
597
640
  event.emitAccomplishment({
@@ -622,7 +665,7 @@ const AccomplishmentTracker = () => {
622
665
 
623
666
  ```typescript
624
667
  const SidebarIntegration = () => {
625
- const { event } = usePlugin();
668
+ const { event } = useRimori();
626
669
 
627
670
  const openTranslator = (text: string) => {
628
671
  // Trigger translator plugin in sidebar
@@ -652,7 +695,7 @@ const SidebarIntegration = () => {
652
695
  Share and discover content created by other users:
653
696
 
654
697
  ```typescript
655
- const { community } = usePlugin();
698
+ const { community } = useRimori();
656
699
 
657
700
  // Shared content methods
658
701
  community.sharedContent.get<T>(contentType: string, id: string): Promise<BasicAssignment<T>>
@@ -679,7 +722,7 @@ interface Exercise {
679
722
  }
680
723
 
681
724
  const ExerciseManager = () => {
682
- const { community } = usePlugin();
725
+ const { community } = useRimori();
683
726
  const [exercises, setExercises] = useState<BasicAssignment<Exercise>[]>([]);
684
727
 
685
728
  // Load community exercises
@@ -997,7 +1040,7 @@ import {
997
1040
  import { HashRouter, Route, Routes } from 'react-router-dom';
998
1041
 
999
1042
  const StudyNotesPlugin = () => {
1000
- const { db, llm, plugin, community } = usePlugin();
1043
+ const { db, llm, plugin, community } = useRimori();
1001
1044
  const [notes, setNotes] = useState([]);
1002
1045
  const [isLoading, setIsLoading] = useState(true);
1003
1046
  const { messages, append } = useChat();
@@ -143,7 +143,6 @@ export function registerDeveloper(jwtToken, port) {
143
143
  return __awaiter(this, void 0, void 0, function* () {
144
144
  console.log('🚀 Registering developer and creating plugin...');
145
145
  try {
146
- console.log('port', port, typeof port);
147
146
  const currentFolderName = path.basename(process.cwd());
148
147
  const body = { port, pluginName: currentFolderName };
149
148
  const backendUrl = process.env.RIMORI_BACKEND_URL || "https://api.rimori.se";
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import 'dotenv/config';
@@ -18,6 +18,7 @@ import { updatePackageJson } from './package-setup.js';
18
18
  import { transformAppRouter } from './router-transformer.js';
19
19
  import { updateTailwindConfig } from './tailwind-config.js';
20
20
  import { updateViteConfigBase } from './vite-config.js';
21
+ import 'dotenv/config';
21
22
  /**
22
23
  * Main function that handles the complete plugin setup flow.
23
24
  */
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ /**
3
+ * Example component demonstrating Logger usage in a React component.
4
+ * Shows how console methods are automatically captured by the Logger.
5
+ */
6
+ export declare const LoggerExample: React.FC;
@@ -0,0 +1,79 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ import { useState } from 'react';
12
+ import { useLogging, useLogger } from '../hooks/UseLogger';
13
+ /**
14
+ * Example component demonstrating Logger usage in a React component.
15
+ * Shows how console methods are automatically captured by the Logger.
16
+ */
17
+ export const LoggerExample = () => {
18
+ const [logMessage, setLogMessage] = useState('Hello from Logger Example');
19
+ const [logData, setLogData] = useState('{"key": "value"}');
20
+ // Initialize the logger - this overrides console methods globally
21
+ const logger = useLogger(process.env.NODE_ENV === 'production');
22
+ // Get logging utilities
23
+ const { sendAllLogs, getStats, clearLogs, exportLogs, sendLogsByLevel } = useLogging(process.env.NODE_ENV === 'production');
24
+ const handleConsoleLog = () => {
25
+ // This will be automatically captured by the Logger
26
+ console.log(logMessage, logData ? JSON.parse(logData) : '');
27
+ };
28
+ const handleConsoleInfo = () => {
29
+ // This will be automatically captured by the Logger
30
+ console.info(logMessage, logData ? JSON.parse(logData) : '');
31
+ };
32
+ const handleConsoleWarn = () => {
33
+ // This will be automatically captured by the Logger and include screenshot + mouse position
34
+ console.warn(logMessage, logData ? JSON.parse(logData) : '');
35
+ };
36
+ const handleConsoleError = () => {
37
+ // This will be automatically captured by the Logger and include screenshot + mouse position
38
+ console.error(logMessage, logData ? JSON.parse(logData) : '');
39
+ };
40
+ const handleConsoleDebug = () => {
41
+ // This will be automatically captured by the Logger
42
+ console.debug(logMessage, logData ? JSON.parse(logData) : '');
43
+ };
44
+ const handleSendLogs = () => __awaiter(void 0, void 0, void 0, function* () {
45
+ yield sendAllLogs();
46
+ alert('Logs sent to Rimori!');
47
+ });
48
+ const handleGetStats = () => {
49
+ const stats = getStats();
50
+ alert(`Log Statistics:\nTotal: ${stats.total}\nDebug: ${stats.byLevel.debug}\nInfo: ${stats.byLevel.info}\nWarn: ${stats.byLevel.warn}\nError: ${stats.byLevel.error}`);
51
+ };
52
+ const handleClearLogs = () => {
53
+ clearLogs();
54
+ alert('Logs cleared!');
55
+ };
56
+ const handleExportLogs = () => {
57
+ const exportedLogs = exportLogs();
58
+ const blob = new Blob([exportedLogs], { type: 'application/json' });
59
+ const url = URL.createObjectURL(blob);
60
+ const a = document.createElement('a');
61
+ a.href = url;
62
+ a.download = 'rimori-logs.json';
63
+ a.click();
64
+ URL.revokeObjectURL(url);
65
+ };
66
+ const handleSendErrorLogs = () => __awaiter(void 0, void 0, void 0, function* () {
67
+ yield sendLogsByLevel('error');
68
+ alert('Error logs sent to Rimori!');
69
+ });
70
+ const handleTriggerError = () => {
71
+ // This will trigger an unhandled error that the logger will catch via console.error override
72
+ throw new Error('This is a test error for the logger');
73
+ };
74
+ const handleTriggerPromiseRejection = () => {
75
+ // This will trigger an unhandled promise rejection that the logger will catch
76
+ Promise.reject(new Error('This is a test promise rejection'));
77
+ };
78
+ return (_jsxs("div", { style: { padding: '20px', maxWidth: '800px' }, children: [_jsx("h2", { children: "Logger Example - Console Override Demo" }), _jsxs("div", { style: { marginBottom: '20px', padding: '15px', backgroundColor: '#e3f2fd', borderRadius: '5px' }, children: [_jsx("h4", { children: "\uD83C\uDFAF Key Feature: Console Methods Override" }), _jsxs("p", { children: ["The Logger automatically overrides ", _jsx("code", { children: "console.log" }), ", ", _jsx("code", { children: "console.info" }), ",", _jsx("code", { children: "console.warn" }), ", ", _jsx("code", { children: "console.error" }), ", and ", _jsx("code", { children: "console.debug" }), " globally. All console calls are automatically captured and stored with context information."] })] }), _jsx("div", { style: { marginBottom: '20px' }, children: _jsxs("label", { children: ["Log Message:", _jsx("input", { type: "text", value: logMessage, onChange: (e) => setLogMessage(e.target.value), placeholder: "Enter log message", style: { width: '100%', marginTop: '5px' } })] }) }), _jsx("div", { style: { marginBottom: '20px' }, children: _jsxs("label", { children: ["Log Data (JSON):", _jsx("textarea", { value: logData, onChange: (e) => setLogData(e.target.value), placeholder: '{"key": "value"}', style: { width: '100%', height: '60px', marginTop: '5px' } })] }) }), _jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h3", { children: "Console Method Calls (Automatically Captured)" }), _jsx("button", { onClick: handleConsoleLog, style: { margin: '5px' }, children: "console.log()" }), _jsx("button", { onClick: handleConsoleInfo, style: { margin: '5px' }, children: "console.info()" }), _jsx("button", { onClick: handleConsoleWarn, style: { margin: '5px', backgroundColor: '#ff9800' }, children: "console.warn() \uD83D\uDCF8" }), _jsx("button", { onClick: handleConsoleError, style: { margin: '5px', backgroundColor: '#f44336' }, children: "console.error() \uD83D\uDCF8" }), _jsx("button", { onClick: handleConsoleDebug, style: { margin: '5px' }, children: "console.debug()" })] }), _jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h3", { children: "Log Management" }), _jsx("button", { onClick: handleSendLogs, style: { margin: '5px' }, children: "Send All Logs to Rimori" }), _jsx("button", { onClick: handleSendErrorLogs, style: { margin: '5px', backgroundColor: '#f44336' }, children: "Send Error Logs Only" }), _jsx("button", { onClick: handleGetStats, style: { margin: '5px' }, children: "Get Log Statistics" }), _jsx("button", { onClick: handleClearLogs, style: { margin: '5px' }, children: "Clear Logs" }), _jsx("button", { onClick: handleExportLogs, style: { margin: '5px' }, children: "Export Logs (JSON)" })] }), _jsxs("div", { style: { marginBottom: '20px' }, children: [_jsx("h3", { children: "Test Error Handling" }), _jsx("button", { onClick: handleTriggerError, style: { margin: '5px', backgroundColor: '#ff6b6b' }, children: "Trigger Unhandled Error" }), _jsx("button", { onClick: handleTriggerPromiseRejection, style: { margin: '5px', backgroundColor: '#ff6b6b' }, children: "Trigger Promise Rejection" })] }), _jsxs("div", { style: { marginTop: '30px', padding: '15px', backgroundColor: '#f5f5f5', borderRadius: '5px' }, children: [_jsx("h4", { children: "\uD83D\uDCCB How It Works:" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Automatic Capture:" }), " All console calls are automatically captured by the Logger"] }), _jsxs("li", { children: [_jsx("strong", { children: "No Manual Logging:" }), " Just use ", _jsx("code", { children: "console.log()" }), " as usual - no need to call Logger methods"] }), _jsxs("li", { children: [_jsx("strong", { children: "Production Filtering:" }), " In production, only warnings and errors show in console, but all are stored"] }), _jsxs("li", { children: [_jsx("strong", { children: "Screenshot Capture:" }), " Errors and warnings automatically capture screenshots + mouse position"] }), _jsxs("li", { children: [_jsx("strong", { children: "Browser Context:" }), " Each log includes URL, user agent, screen resolution, etc."] }), _jsxs("li", { children: [_jsx("strong", { children: "Rimori Integration:" }), " Logs can be sent to Rimori for centralized logging"] })] }), _jsx("h4", { children: "\uD83C\uDFAF Screenshot & Mouse Position:" }), _jsxs("p", { children: ["When you call ", _jsx("code", { children: "console.warn()" }), " or ", _jsx("code", { children: "console.error()" }), ", the Logger automatically:"] }), _jsxs("ul", { children: [_jsx("li", { children: "Captures a screenshot of the current page (if html2canvas is available)" }), _jsx("li", { children: "Records the current mouse position (x, y coordinates)" }), _jsx("li", { children: "Includes timestamp for both screenshot and mouse position" })] })] }), _jsxs("div", { style: { marginTop: '20px', padding: '15px', backgroundColor: '#fff3e0', borderRadius: '5px' }, children: [_jsx("h4", { children: "\uD83D\uDD27 Setup Required:" }), _jsx("p", { children: "To enable screenshot capture, include html2canvas in your plugin:" }), _jsx("pre", { style: { backgroundColor: '#f5f5f5', padding: '10px', borderRadius: '3px' }, children: `<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>` })] })] }));
79
+ };
@@ -5,12 +5,12 @@ import { AudioInputField } from './EmbeddedAssistent/AudioInputField';
5
5
  import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
6
6
  import Markdown from 'react-markdown';
7
7
  import { useChat } from '../../hooks/UseChatHook';
8
- import { usePlugin } from '../../components';
8
+ import { useRimori } from '../../components';
9
9
  import { getFirstMessages } from './utils';
10
10
  export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }) {
11
11
  var _a;
12
12
  const [oralCommunication, setOralCommunication] = React.useState(true);
13
- const { ai: llm, event } = usePlugin();
13
+ const { ai: llm, event } = useRimori();
14
14
  const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
15
15
  const { messages, append, isLoading, setMessages } = useChat();
16
16
  const lastAssistantMessage = (_a = [...messages].filter((m) => m.role === 'assistant').pop()) === null || _a === void 0 ? void 0 : _a.content;
@@ -4,10 +4,10 @@ import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecoder';
4
4
  import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
5
5
  import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
6
6
  import { useChat } from '../../hooks/UseChatHook';
7
- import { usePlugin } from '../../components';
7
+ import { useRimori } from '../../components';
8
8
  import { getFirstMessages } from './utils';
9
9
  export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversation, children, isDarkTheme = false, circleSize = "300px", className }) {
10
- const { ai, event } = usePlugin();
10
+ const { ai, event } = useRimori();
11
11
  const [agentReplying, setAgentReplying] = useState(false);
12
12
  const [isProcessingMessage, setIsProcessingMessage] = useState(false);
13
13
  const sender = useMemo(() => new MessageSender(ai.getVoice, voiceId), [voiceId]);
@@ -8,52 +8,61 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
- import { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
11
+ import { useRimori } from '../../../components';
12
12
  import { FaMicrophone, FaSpinner } from 'react-icons/fa6';
13
- import { usePlugin } from '../../../components';
13
+ import { AudioController } from '../../../plugin/AudioController';
14
+ import { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
14
15
  export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange, enablePushToTalk = false }, ref) => {
15
16
  const [isRecording, setIsRecording] = useState(false);
16
17
  const [internalIsProcessing, setInternalIsProcessing] = useState(false);
17
- const mediaRecorderRef = useRef(null);
18
- const audioChunksRef = useRef([]);
19
- const mediaStreamRef = useRef(null);
20
- const { ai: llm } = usePlugin();
18
+ const audioControllerRef = useRef(null);
19
+ const { ai, plugin } = useRimori();
21
20
  // Ref for latest onVoiceRecorded callback
22
21
  const onVoiceRecordedRef = useRef(onVoiceRecorded);
23
22
  useEffect(() => {
24
23
  onVoiceRecordedRef.current = onVoiceRecorded;
25
24
  }, [onVoiceRecorded]);
26
25
  const startRecording = () => __awaiter(void 0, void 0, void 0, function* () {
27
- const stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
28
- mediaStreamRef.current = stream;
29
- const mediaRecorder = new MediaRecorder(stream);
30
- mediaRecorderRef.current = mediaRecorder;
31
- mediaRecorder.ondataavailable = (event) => {
32
- audioChunksRef.current.push(event.data);
33
- };
34
- mediaRecorder.onstop = () => __awaiter(void 0, void 0, void 0, function* () {
35
- const audioBlob = new Blob(audioChunksRef.current);
36
- audioChunksRef.current = [];
37
- setInternalIsProcessing(true);
38
- const text = yield llm.getTextFromVoice(audioBlob);
39
- setInternalIsProcessing(false);
40
- onVoiceRecordedRef.current(text);
41
- });
42
- mediaRecorder.start();
43
- setIsRecording(true);
44
- onRecordingStatusChange(true);
26
+ try {
27
+ if (!audioControllerRef.current) {
28
+ audioControllerRef.current = new AudioController(plugin.pluginId);
29
+ }
30
+ yield audioControllerRef.current.startRecording();
31
+ setIsRecording(true);
32
+ onRecordingStatusChange(true);
33
+ }
34
+ catch (error) {
35
+ console.error('Failed to start recording:', error);
36
+ // Handle permission denied or other errors
37
+ }
45
38
  });
46
- const stopRecording = () => {
47
- if (mediaRecorderRef.current) {
48
- mediaRecorderRef.current.stop();
39
+ const stopRecording = () => __awaiter(void 0, void 0, void 0, function* () {
40
+ try {
41
+ if (audioControllerRef.current && isRecording) {
42
+ const audioResult = yield audioControllerRef.current.stopRecording();
43
+ // console.log("audioResult: ", audioResult);
44
+ setInternalIsProcessing(true);
45
+ // Play the recorded audio from the Blob
46
+ // const blobUrl = URL.createObjectURL(audioResult.recording);
47
+ // const audioRef = new Audio(blobUrl);
48
+ // audioRef.onended = () => URL.revokeObjectURL(blobUrl);
49
+ // audioRef.play().catch((e) => console.error('Playback error:', e));
50
+ // console.log("audioBlob: ", audioResult.recording);
51
+ const text = yield ai.getTextFromVoice(audioResult.recording);
52
+ // console.log("stt result", text);
53
+ // throw new Error("test");
54
+ setInternalIsProcessing(false);
55
+ onVoiceRecordedRef.current(text);
56
+ }
57
+ }
58
+ catch (error) {
59
+ console.error('Failed to stop recording:', error);
60
+ }
61
+ finally {
49
62
  setIsRecording(false);
50
63
  onRecordingStatusChange(false);
51
64
  }
52
- if (mediaStreamRef.current) {
53
- mediaStreamRef.current.getTracks().forEach(track => track.stop());
54
- mediaStreamRef.current = null;
55
- }
56
- };
65
+ });
57
66
  useImperativeHandle(ref, () => ({
58
67
  startRecording,
59
68
  stopRecording,
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { useState, useEffect } from 'react';
12
12
  import { FaPlayCircle, FaStopCircle } from "react-icons/fa";
13
- import { usePlugin } from "../../providers/PluginProvider";
13
+ import { useRimori } from "../../providers/PluginProvider";
14
14
  import { Spinner } from '../Spinner';
15
15
  import { EventBus } from '../../fromRimori/EventBus';
16
16
  export const AudioPlayOptions = [0.8, 0.9, 1.0, 1.1, 1.2, 1.5];
@@ -20,7 +20,7 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
20
20
  const [speed, setSpeed] = useState(initialSpeed);
21
21
  const [isPlaying, setIsPlaying] = useState(false);
22
22
  const [isLoading, setIsLoading] = useState(false);
23
- const { ai } = usePlugin();
23
+ const { ai } = useRimori();
24
24
  useEffect(() => {
25
25
  if (!playListenerEvent)
26
26
  return;
@@ -6,18 +6,43 @@ const ContextMenu = ({ client }) => {
6
6
  const [actions, setActions] = useState([]);
7
7
  const [position, setPosition] = useState({ x: 0, y: 0 });
8
8
  const [openOnTextSelect, setOpenOnTextSelect] = useState(false);
9
+ const [menuWidth, setMenuWidth] = useState(0);
9
10
  const menuRef = useRef(null);
11
+ const isMobile = window.innerWidth < 768;
12
+ /**
13
+ * Calculates position for mobile context menu based on selected text bounds.
14
+ * Centers the menu horizontally over the selected text and positions it 30px below the text's end.
15
+ * @param selectedText - The currently selected text
16
+ * @param menuWidth - The width of the menu to center properly
17
+ * @returns Position object with x and y coordinates
18
+ */
19
+ const calculateMobilePosition = (selectedText, menuWidth = 0) => {
20
+ const selection = window.getSelection();
21
+ if (!selection || !selectedText) {
22
+ return { x: 0, y: 0, text: selectedText };
23
+ }
24
+ const range = selection.getRangeAt(0);
25
+ const rect = range.getBoundingClientRect();
26
+ // Center horizontally over the selected text, accounting for menu width
27
+ const centerX = rect.left + (rect.width / 2) - (menuWidth / 2);
28
+ // Position 12px below where the text ends vertically
29
+ const textEndY = rect.bottom + 12;
30
+ return { x: centerX, y: textEndY, text: selectedText };
31
+ };
10
32
  useEffect(() => {
11
- client.plugin.getInstalled().then(plugins => {
12
- setActions(plugins.flatMap(p => p.context_menu_actions).filter(Boolean));
13
- });
14
- client.plugin.getUserInfo().then((userInfo) => {
15
- setOpenOnTextSelect(userInfo.context_menu_on_select);
16
- });
33
+ const actions = client.plugin.getPluginInfo().installedPlugins.flatMap(p => p.context_menu_actions).filter(Boolean);
34
+ setActions(actions);
35
+ setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
17
36
  EventBus.on("global.contextMenu.createActions", ({ data }) => {
18
37
  setActions([...data.actions, ...actions]);
19
38
  });
20
39
  }, []);
40
+ // Update menu width when menu is rendered
41
+ useEffect(() => {
42
+ if (isOpen && menuRef.current) {
43
+ setMenuWidth(menuRef.current.offsetWidth);
44
+ }
45
+ }, [isOpen, actions]);
21
46
  useEffect(() => {
22
47
  // Track mouse position globally
23
48
  const handleMouseMove = (e) => {
@@ -25,7 +50,12 @@ const ContextMenu = ({ client }) => {
25
50
  const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
26
51
  if (isOpen && selectedText === position.text)
27
52
  return;
28
- setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
53
+ if (isMobile && selectedText) {
54
+ setPosition(calculateMobilePosition(selectedText, menuWidth));
55
+ }
56
+ else {
57
+ setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
58
+ }
29
59
  };
30
60
  const handleMouseUp = (e) => {
31
61
  var _a, _b;
@@ -51,20 +81,29 @@ const ContextMenu = ({ client }) => {
51
81
  if (e.button === 2) {
52
82
  e.preventDefault();
53
83
  }
54
- setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
84
+ if (isMobile) {
85
+ setPosition(calculateMobilePosition(selectedText, menuWidth));
86
+ }
87
+ else {
88
+ setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
89
+ }
55
90
  setIsOpen(true);
56
91
  }
57
92
  else {
58
93
  setIsOpen(false);
59
94
  }
60
95
  };
61
- // Add selectionchange listener to close menu if selection is cleared
96
+ // Add selectionchange listener to close menu if selection is cleared and update position for mobile
62
97
  const handleSelectionChange = () => {
63
98
  var _a;
64
99
  const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
65
100
  if (!selectedText && isOpen) {
66
101
  setIsOpen(false);
67
102
  }
103
+ else if (selectedText && isOpen && isMobile) {
104
+ // Update position in real-time as text selection changes on mobile
105
+ setPosition(calculateMobilePosition(selectedText, menuWidth));
106
+ }
68
107
  };
69
108
  document.addEventListener("mouseup", handleMouseUp);
70
109
  window.addEventListener("mousemove", handleMouseMove);