@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
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 `
|
|
211
|
+
The `useRimori()` hook is the main interface for accessing Rimori platform features:
|
|
175
212
|
|
|
176
213
|
```typescript
|
|
177
|
-
import {
|
|
214
|
+
import { useRimori } from "@rimori/client";
|
|
178
215
|
|
|
179
216
|
const MyComponent = () => {
|
|
180
|
-
const client =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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
|
-
|
|
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,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 {
|
|
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 } =
|
|
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 {
|
|
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 } =
|
|
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 {
|
|
11
|
+
import { useRimori } from '../../../components';
|
|
12
12
|
import { FaMicrophone, FaSpinner } from 'react-icons/fa6';
|
|
13
|
-
import {
|
|
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
|
|
18
|
-
const
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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 {
|
|
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 } =
|
|
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.
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|