@rimori/client 1.4.10 → 2.1.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 +140 -1171
- package/dist/{core/controller → controller}/AIController.d.ts +1 -1
- package/dist/{core/controller → controller}/AIController.js +2 -2
- package/dist/{plugin/AccomplishmentHandler.d.ts → controller/AccomplishmentController.d.ts} +1 -1
- package/dist/{plugin/AccomplishmentHandler.js → controller/AccomplishmentController.js} +1 -1
- package/dist/{core/controller → controller}/ExerciseController.d.ts +8 -6
- package/dist/{core/controller → controller}/ExerciseController.js +10 -9
- package/dist/{core/controller → controller}/ObjectController.d.ts +1 -1
- package/dist/{core/controller → controller}/ObjectController.js +1 -1
- package/dist/{core/controller → controller}/SettingsController.d.ts +2 -2
- package/dist/{core/controller → controller}/SharedContentController.d.ts +1 -1
- package/dist/{core/controller → controller}/VoiceController.d.ts +1 -1
- package/dist/index.d.ts +14 -8
- package/dist/index.js +7 -7
- package/dist/plugin/{PluginController.d.ts → CommunicationHandler.d.ts} +4 -7
- package/dist/plugin/{PluginController.js → CommunicationHandler.js} +19 -27
- package/dist/plugin/RimoriClient.d.ts +67 -68
- package/dist/plugin/RimoriClient.js +101 -43
- package/dist/worker/WorkerSetup.js +3 -3
- package/example/docs/devdocs.md +1 -1
- package/package.json +5 -26
- package/src/{core/controller → controller}/AIController.ts +3 -3
- package/src/{plugin/AccomplishmentHandler.ts → controller/AccomplishmentController.ts} +1 -1
- package/src/{core/controller → controller}/ExerciseController.ts +14 -11
- package/src/{core/controller → controller}/ObjectController.ts +2 -2
- package/src/{core/controller → controller}/SettingsController.ts +2 -2
- package/src/{core/controller → controller}/SharedContentController.ts +1 -1
- package/src/{core/controller → controller}/VoiceController.ts +2 -2
- package/src/fromRimori/readme.md +1 -1
- package/src/index.ts +14 -8
- package/src/plugin/{PluginController.ts → CommunicationHandler.ts} +24 -36
- package/src/plugin/RimoriClient.ts +127 -118
- package/src/worker/WorkerSetup.ts +6 -4
- package/tsconfig.json +5 -2
- package/dist/cli/scripts/release/release-translation-upload.d.ts +0 -6
- package/dist/cli/scripts/release/release-translation-upload.js +0 -87
- package/dist/components/CRUDModal.d.ts +0 -17
- package/dist/components/CRUDModal.js +0 -24
- package/dist/components/MarkdownEditor.d.ts +0 -8
- package/dist/components/MarkdownEditor.js +0 -48
- package/dist/components/Spinner.d.ts +0 -8
- package/dist/components/Spinner.js +0 -4
- package/dist/components/ai/Assistant.d.ts +0 -9
- package/dist/components/ai/Assistant.js +0 -58
- package/dist/components/ai/Avatar.d.ts +0 -14
- package/dist/components/ai/Avatar.js +0 -59
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.d.ts +0 -7
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +0 -37
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -8
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +0 -79
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +0 -91
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +0 -27
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +0 -185
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +0 -11
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +0 -95
- package/dist/components/ai/utils.d.ts +0 -6
- package/dist/components/ai/utils.js +0 -13
- package/dist/components/audio/Playbutton.d.ts +0 -15
- package/dist/components/audio/Playbutton.js +0 -80
- package/dist/components/components/ContextMenu.d.ts +0 -10
- package/dist/components/components/ContextMenu.js +0 -135
- package/dist/components.d.ts +0 -10
- package/dist/components.js +0 -11
- package/dist/core/controller/EnhancedUserInfo.d.ts +0 -0
- package/dist/core/controller/EnhancedUserInfo.js +0 -1
- package/dist/core/core.d.ts +0 -14
- package/dist/core/core.js +0 -7
- package/dist/hooks/I18nHooks.d.ts +0 -11
- package/dist/hooks/I18nHooks.js +0 -25
- package/dist/hooks/UseChatHook.d.ts +0 -10
- package/dist/hooks/UseChatHook.js +0 -29
- package/dist/i18n/I18nHooks.d.ts +0 -11
- package/dist/i18n/I18nHooks.js +0 -25
- package/dist/i18n/Translator.d.ts +0 -43
- package/dist/i18n/Translator.js +0 -118
- package/dist/i18n/config.d.ts +0 -7
- package/dist/i18n/config.js +0 -20
- package/dist/i18n/createI18nInstance.d.ts +0 -7
- package/dist/i18n/createI18nInstance.js +0 -31
- package/dist/i18n/hooks.d.ts +0 -11
- package/dist/i18n/hooks.js +0 -25
- package/dist/i18n/index.d.ts +0 -4
- package/dist/i18n/index.js +0 -4
- package/dist/i18n/types.d.ts +0 -7
- package/dist/i18n/types.js +0 -1
- package/dist/i18n/useRimoriI18n.d.ts +0 -11
- package/dist/i18n/useRimoriI18n.js +0 -41
- package/dist/plugin/ThemeSetter.d.ts +0 -2
- package/dist/plugin/ThemeSetter.js +0 -19
- package/dist/plugin/Translator.d.ts +0 -38
- package/dist/plugin/Translator.js +0 -101
- package/dist/providers/PluginProvider.d.ts +0 -12
- package/dist/providers/PluginProvider.js +0 -152
- package/dist/style.css +0 -110
- package/dist/style.css.map +0 -1
- package/dist/utils/Language.d.ts +0 -67
- package/dist/utils/Language.js +0 -69
- package/dist/utils/LanguageClass.d.ts +0 -36
- package/dist/utils/LanguageClass.example.d.ts +0 -0
- package/dist/utils/LanguageClass.example.js +0 -1
- package/dist/utils/LanguageClass.js +0 -50
- package/dist/utils/LanguageClass.test.d.ts +0 -0
- package/dist/utils/LanguageClass.test.js +0 -1
- package/dist/utils/PluginUtils.d.ts +0 -2
- package/dist/utils/PluginUtils.js +0 -23
- package/src/components/CRUDModal.tsx +0 -75
- package/src/components/MarkdownEditor.tsx +0 -144
- package/src/components/Spinner.tsx +0 -29
- package/src/components/ai/Assistant.tsx +0 -96
- package/src/components/ai/Avatar.tsx +0 -99
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +0 -73
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +0 -107
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +0 -96
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +0 -197
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +0 -129
- package/src/components/ai/utils.ts +0 -21
- package/src/components/audio/Playbutton.tsx +0 -126
- package/src/components/components/ContextMenu.tsx +0 -179
- package/src/components.ts +0 -11
- package/src/core/core.ts +0 -15
- package/src/hooks/I18nHooks.ts +0 -33
- package/src/hooks/UseChatHook.ts +0 -38
- package/src/plugin/ThemeSetter.ts +0 -23
- package/src/providers/PluginProvider.tsx +0 -209
- package/src/style.scss +0 -136
- package/src/utils/PluginUtils.ts +0 -22
- /package/dist/{plugin → controller}/AudioController.d.ts +0 -0
- /package/dist/{plugin → controller}/AudioController.js +0 -0
- /package/dist/{core/controller → controller}/SettingsController.js +0 -0
- /package/dist/{core/controller → controller}/SharedContentController.js +0 -0
- /package/dist/{plugin → controller}/TranslationController.d.ts +0 -0
- /package/dist/{plugin → controller}/TranslationController.js +0 -0
- /package/dist/{core/controller → controller}/VoiceController.js +0 -0
- /package/src/{plugin → controller}/AudioController.ts +0 -0
- /package/src/{plugin → controller}/TranslationController.ts +0 -0
package/README.md
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
# Rimori Client Package
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The `@rimori/client` package is the framework-agnostic runtime and CLI that powers Rimori plugins. Use it inside plugin iframes, workers, and build scripts to access Rimori platform features such as database access, AI, shared content, and the event bus. All React-specific helpers and UI components are now published separately in `@rimori/react-client`.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
|
-
|
|
6
|
+
- [Overview](#overview)
|
|
7
7
|
- [Installation](#installation)
|
|
8
|
-
- [
|
|
9
|
-
- [Releasing Your Plugin to Rimori](#releasing-your-plugin-to-rimori)
|
|
8
|
+
- [Relationship to @rimori/react-client](#relationship-to-rimori-react-client)
|
|
10
9
|
- [Quick Start](#quick-start)
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
10
|
+
- [CLI Tooling](#cli-tooling)
|
|
11
|
+
- [Runtime API](#runtime-api)
|
|
12
|
+
- [Bootstrapping](#bootstrapping)
|
|
13
|
+
- [Plugin Interface](#plugin-interface)
|
|
14
|
+
- [Database Access](#database-access)
|
|
15
|
+
- [AI & Voice](#ai--voice)
|
|
16
|
+
- [Event Bus & Actions](#event-bus--actions)
|
|
17
|
+
- [Community Content](#community-content)
|
|
18
|
+
- [Workers & Standalone Development](#workers--standalone-development)
|
|
18
19
|
- [Utilities](#utilities)
|
|
19
20
|
- [TypeScript Support](#typescript-support)
|
|
20
|
-
- [
|
|
21
|
+
- [Example Integration](#example-integration)
|
|
22
|
+
- [Troubleshooting](#troubleshooting)
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
`@rimori/client` gives you direct, typed access to the Rimori platform:
|
|
27
|
+
- Bootstrap authenticated plugin sessions and fetch Rimori context.
|
|
28
|
+
- Run Supabase queries against your plugin's dedicated schema.
|
|
29
|
+
- Call AI services for text, structured data, or voice.
|
|
30
|
+
- Communicate with Rimori and other plugins through the event bus.
|
|
31
|
+
- Share content with the community and emit accomplishments.
|
|
32
|
+
- Ship and upgrade plugins by using the bundled CLI.
|
|
21
33
|
|
|
22
34
|
## Installation
|
|
23
35
|
|
|
@@ -27,1239 +39,196 @@ npm install @rimori/client
|
|
|
27
39
|
yarn add @rimori/client
|
|
28
40
|
```
|
|
29
41
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
The Rimori Client package includes powerful CLI tools to eliminate the tedious setup process and get you building your plugin fast. The initialization script handles authentication, plugin registration, environment setup, and all necessary boilerplate configuration.
|
|
33
|
-
|
|
34
|
-
### Prerequisites
|
|
35
|
-
|
|
36
|
-
Before initializing your plugin, ensure you have:
|
|
37
|
-
|
|
38
|
-
1. **Node.js and yarn/npm installed**
|
|
39
|
-
2. **A Rimori account** - You'll need to login during initialization to receive your access token
|
|
40
|
-
|
|
41
|
-
### Initializing a New Plugin
|
|
42
|
+
## Relationship to @rimori/react-client
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Then connect it to your Github account.
|
|
46
|
-
|
|
47
|
-
Clone the git repository:
|
|
44
|
+
If you are building a React-based plugin UI, install the companion package:
|
|
48
45
|
|
|
49
46
|
```bash
|
|
50
|
-
|
|
51
|
-
cd my-awesome-plugin
|
|
52
|
-
|
|
53
|
-
# Initialize with Rimori Client (this handles everything!)
|
|
54
|
-
npx @rimori/client rimori-init
|
|
47
|
+
npm install @rimori/react-client
|
|
55
48
|
```
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
`@rimori/react-client` wraps this core runtime with React context, hooks, and prebuilt UI components. Use it for UI concerns (`PluginProvider`, `useRimori`, `useChat`, widgets). Keep importing non-UI functionality such as `RimoriClient`, `StandaloneClient`, `setupWorker`, or the CLI directly from `@rimori/client`.
|
|
58
51
|
|
|
59
|
-
|
|
52
|
+
## Quick Start
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
2. **🚀 Plugin Registration**: Automatically registers your plugin and generates a unique plugin ID
|
|
63
|
-
3. **🔑 Access Token**: Provides you with an access token for future plugin releases
|
|
64
|
-
4. **📦 Package Configuration**: Updates `package.json` with plugin-specific settings
|
|
65
|
-
5. **⚙️ Environment Setup**: Creates `.env` files with your credentials
|
|
66
|
-
6. **📁 File Structure**: Copies all necessary boilerplate files and examples
|
|
67
|
-
7. **🎨 Configuration**: Sets up Vite, Tailwind, and router configurations
|
|
68
|
-
8. **📖 Documentation**: Provides example documentation and getting started guides
|
|
54
|
+
Instantiate the client once in your application entry point and reuse it everywhere:
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
```ts
|
|
57
|
+
import { RimoriClient } from "@rimori/client";
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
yarn rimori-init --upgrade
|
|
76
|
-
```
|
|
59
|
+
async function bootstrap() {
|
|
60
|
+
const client = await RimoriClient.getInstance("your-plugin-id");
|
|
77
61
|
|
|
78
|
-
|
|
62
|
+
const user = client.plugin.getUserInfo();
|
|
63
|
+
const { data } = await client.db
|
|
64
|
+
.from("notes")
|
|
65
|
+
.select("*")
|
|
66
|
+
.eq("user_id", user.profile_id);
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
# Start development server
|
|
84
|
-
yarn dev
|
|
68
|
+
console.log("Loaded notes", data);
|
|
69
|
+
}
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
# http://localhost:3000 (or your chosen port)
|
|
71
|
+
bootstrap().catch(console.error);
|
|
88
72
|
```
|
|
89
73
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- ✅ **Hot reload** for instant development feedback
|
|
93
|
-
- ✅ **TypeScript support** with full type safety
|
|
94
|
-
- ✅ **TailwindCSS** for modern styling
|
|
95
|
-
- ✅ **React Router** for navigation
|
|
96
|
-
- ✅ **Example components** and documentation
|
|
74
|
+
The instance exposes high-level controllers grouped under properties such as `plugin`, `db`, `ai`, `event`, `community`, `runtime`, and `navigation`.
|
|
97
75
|
|
|
98
|
-
##
|
|
76
|
+
## CLI Tooling
|
|
99
77
|
|
|
100
|
-
|
|
78
|
+
Two CLI commands ship with the package (also available through `npx`):
|
|
101
79
|
|
|
102
|
-
###
|
|
80
|
+
### `rimori-init`
|
|
103
81
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
82
|
+
- Authenticates against Rimori using your developer credentials.
|
|
83
|
+
- Registers the plugin and writes the plugin ID (`r_id`) into `package.json`.
|
|
84
|
+
- Provisions environment files, Vite/Tailwind scaffolding, worker configuration, and sample assets.
|
|
107
85
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
During plugin initialization, convenient release scripts are automatically added to your `package.json`. These scripts handle building and releasing in one command:
|
|
86
|
+
Usage:
|
|
111
87
|
|
|
112
88
|
```bash
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
yarn release:beta # Build and release to beta channel
|
|
116
|
-
yarn release:stable # Build and release to stable channel
|
|
89
|
+
npx @rimori/client rimori-init
|
|
90
|
+
npx @rimori/client rimori-init --upgrade # refresh config without changing the plugin ID
|
|
117
91
|
```
|
|
118
92
|
|
|
119
|
-
###
|
|
93
|
+
### `rimori-release`
|
|
94
|
+
|
|
95
|
+
- Builds (optionally) and uploads the plugin bundle to Rimori.
|
|
96
|
+
- Updates release metadata, database migrations, and activates the chosen channel (`alpha`, `beta`, `stable`).
|
|
120
97
|
|
|
121
|
-
|
|
98
|
+
Usage:
|
|
122
99
|
|
|
123
100
|
```bash
|
|
124
|
-
# Build your plugin first
|
|
125
101
|
yarn build
|
|
126
|
-
|
|
127
|
-
# Then release to different channels
|
|
128
|
-
yarn rimori-release alpha # For alpha testing
|
|
129
|
-
yarn rimori-release beta # For beta releases
|
|
130
|
-
yarn rimori-release stable # For production releases
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The plugin version is automatically read from your `package.json`, and the plugin ID is retrieved from the `r_id` field (set during initialization).
|
|
134
|
-
|
|
135
|
-
### What the Release Script Does
|
|
136
|
-
|
|
137
|
-
The `rimori-release` command performs a complete release workflow:
|
|
138
|
-
|
|
139
|
-
1. **📋 Configuration Upload**: Sends plugin metadata and configuration to the platform
|
|
140
|
-
2. **🗄️ Database Updates**: Updates plugin information and release records
|
|
141
|
-
3. **📁 File Upload**: Uploads all files from your `dist/` directory to the platform
|
|
142
|
-
4. **🚀 Plugin Activation**: Activates the new version on the specified release channel
|
|
143
|
-
|
|
144
|
-
### Release Channels
|
|
145
|
-
|
|
146
|
-
- **`alpha`**: Early development releases for internal testing
|
|
147
|
-
- **`beta`**: Pre-release versions for beta testers
|
|
148
|
-
- **`stable`**: Production-ready releases for all users
|
|
149
|
-
|
|
150
|
-
### Automatic Configuration
|
|
151
|
-
|
|
152
|
-
During plugin initialization, the following are automatically configured:
|
|
153
|
-
|
|
154
|
-
- `RIMORI_TOKEN`: Your authentication token (stored in `.env`)
|
|
155
|
-
- `r_id`: Your unique plugin ID (stored in `package.json`)
|
|
156
|
-
- **Release scripts**: `yarn release:alpha`, `yarn release:beta`, `yarn release:stable`
|
|
157
|
-
- **Build configuration**: TypeScript checking, Vite setup, and worker builds
|
|
158
|
-
|
|
159
|
-
### Troubleshooting
|
|
160
|
-
|
|
161
|
-
If you encounter release issues:
|
|
162
|
-
|
|
163
|
-
1. **Missing token**: Ensure `RIMORI_TOKEN` is in your `.env` file
|
|
164
|
-
2. **No plugin ID**: Verify `r_id` exists in your `package.json`
|
|
165
|
-
3. **Build errors**: Run `yarn build` successfully before releasing
|
|
166
|
-
4. **Authentication**: Your token may have expired - re-run `rimori-init` if needed
|
|
167
|
-
|
|
168
|
-
#### Worker Process Errors
|
|
169
|
-
|
|
170
|
-
If you encounter errors like:
|
|
171
|
-
|
|
102
|
+
npx @rimori/client rimori-release alpha
|
|
172
103
|
```
|
|
173
|
-
ReferenceError: process is not defined
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**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`.
|
|
177
|
-
|
|
178
|
-
**Debugging Steps**:
|
|
179
|
-
|
|
180
|
-
1. **Check your imports**: Look through the files used in the worker which might import libraries that might access `process.env`:
|
|
181
|
-
- React libraries (React Router, React Query, etc.)
|
|
182
|
-
- UI component libraries (Material-UI, Ant Design, etc.)
|
|
183
|
-
- Utility libraries that check for environment variables
|
|
184
|
-
|
|
185
|
-
2. **Verify Rimori Client imports**: Ensure you're importing from `@rimori/client/core` and not from `@rimori/client`:
|
|
186
104
|
|
|
187
|
-
|
|
188
|
-
// ✅ Correct - import from rimori client
|
|
189
|
-
import { RimoriClient } from '@rimori/client/core';
|
|
105
|
+
During initialization, convenience scripts (`release:alpha`, `release:beta`, `release:stable`) are added to your project automatically.
|
|
190
106
|
|
|
191
|
-
|
|
192
|
-
import { Avatar } from '@rimori/client';
|
|
193
|
-
import { useQuery } from '@tanstack/react-query';
|
|
194
|
-
```
|
|
107
|
+
## Runtime API
|
|
195
108
|
|
|
196
|
-
|
|
197
|
-
- UI frameworks (React, Vue, etc.)
|
|
198
|
-
- State management libraries
|
|
199
|
-
- Routing libraries
|
|
200
|
-
- Any library that checks `process.env.NODE_ENV`
|
|
109
|
+
### Bootstrapping
|
|
201
110
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
````
|
|
207
|
-
|
|
208
|
-
## Core API - usePlugin Hook
|
|
209
|
-
|
|
210
|
-
The `useRimori()` hook is the main interface for accessing Rimori platform features:
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import { useRimori } from "@rimori/client";
|
|
214
|
-
|
|
215
|
-
const MyComponent = () => {
|
|
216
|
-
const client = useRimori();
|
|
217
|
-
|
|
218
|
-
// Access all client features
|
|
219
|
-
const { db, llm, event, community, plugin } = client;
|
|
220
|
-
|
|
221
|
-
return <div>My Plugin Content</div>;
|
|
222
|
-
};
|
|
223
|
-
````
|
|
111
|
+
- `RimoriClient.getInstance(pluginId)` – connect the sandboxed iframe to Rimori.
|
|
112
|
+
- `StandaloneClient` – authenticate when developing a plugin outside Rimori.
|
|
113
|
+
- `setupWorker()` – register worker scripts that need the Rimori runtime.
|
|
224
114
|
|
|
225
115
|
### Plugin Interface
|
|
226
116
|
|
|
227
|
-
|
|
228
|
-
const { plugin } = useRimori();
|
|
229
|
-
|
|
230
|
-
// Plugin information and settings
|
|
231
|
-
plugin.pluginId: string // Current plugin ID
|
|
232
|
-
plugin.getSettings<T>(defaultSettings: T): Promise<T> // Get plugin settings
|
|
233
|
-
plugin.setSettings(settings: any): Promise<void> // Update plugin settings
|
|
234
|
-
plugin.getInstalled(): Promise<Plugin[]> // Get all installed plugins
|
|
235
|
-
plugin.getUserInfo(): Promise<UserInfo> // Get current user information
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
**Example: Managing Flashcard Plugin Settings**
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
interface FlashcardSettings {
|
|
242
|
-
dailyGoal: number;
|
|
243
|
-
reviewInterval: 'easy' | 'medium' | 'hard';
|
|
244
|
-
showAnswerDelay: number;
|
|
245
|
-
enableAudioPronunciation: boolean;
|
|
246
|
-
difficultyAlgorithm: 'spaced-repetition' | 'random' | 'progressive';
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const FlashcardSettingsComponent = () => {
|
|
250
|
-
const { plugin } = useRimori();
|
|
251
|
-
const [settings, setSettings] = useState<FlashcardSettings>();
|
|
252
|
-
|
|
253
|
-
useEffect(() => {
|
|
254
|
-
const loadSettings = async () => {
|
|
255
|
-
const defaultSettings: FlashcardSettings = {
|
|
256
|
-
dailyGoal: 20,
|
|
257
|
-
reviewInterval: 'medium',
|
|
258
|
-
showAnswerDelay: 3,
|
|
259
|
-
enableAudioPronunciation: true,
|
|
260
|
-
difficultyAlgorithm: 'spaced-repetition'
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const currentSettings = await plugin.getSettings(defaultSettings);
|
|
264
|
-
setSettings(currentSettings);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
loadSettings();
|
|
268
|
-
}, []);
|
|
269
|
-
|
|
270
|
-
const updateSettings = async (newSettings: Partial<FlashcardSettings>) => {
|
|
271
|
-
const updated = { ...settings, ...newSettings };
|
|
272
|
-
await plugin.setSettings(updated);
|
|
273
|
-
setSettings(updated);
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
return (
|
|
277
|
-
<div className="flashcard-settings">
|
|
278
|
-
<label>
|
|
279
|
-
Daily Goal: {settings?.dailyGoal} cards
|
|
280
|
-
<input
|
|
281
|
-
type="range"
|
|
282
|
-
min="5"
|
|
283
|
-
max="100"
|
|
284
|
-
value={settings?.dailyGoal}
|
|
285
|
-
onChange={(e) => updateSettings({ dailyGoal: parseInt(e.target.value) })}
|
|
286
|
-
/>
|
|
287
|
-
</label>
|
|
288
|
-
|
|
289
|
-
<label>
|
|
290
|
-
Review Interval:
|
|
291
|
-
<select
|
|
292
|
-
value={settings?.reviewInterval}
|
|
293
|
-
onChange={(e) => updateSettings({ reviewInterval: e.target.value as any })}
|
|
294
|
-
>
|
|
295
|
-
<option value="easy">Easy (longer intervals)</option>
|
|
296
|
-
<option value="medium">Medium</option>
|
|
297
|
-
<option value="hard">Hard (shorter intervals)</option>
|
|
298
|
-
</select>
|
|
299
|
-
</label>
|
|
300
|
-
|
|
301
|
-
<label>
|
|
302
|
-
<input
|
|
303
|
-
type="checkbox"
|
|
304
|
-
checked={settings?.enableAudioPronunciation}
|
|
305
|
-
onChange={(e) => updateSettings({ enableAudioPronunciation: e.target.checked })}
|
|
306
|
-
/>
|
|
307
|
-
Enable Audio Pronunciation
|
|
308
|
-
</label>
|
|
309
|
-
</div>
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Database Integration
|
|
315
|
-
|
|
316
|
-
Access your plugin's dedicated database tables with full TypeScript support:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
const { db } = useRimori();
|
|
320
|
-
|
|
321
|
-
// Database interface
|
|
322
|
-
db.from(tableName) // Query builder for tables/views - supports ALL Supabase operations
|
|
323
|
-
db.storage // File storage access
|
|
324
|
-
db.tablePrefix: string // Your plugin's table prefix
|
|
325
|
-
db.getTableName(table: string): string // Get full table name with prefix
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
The `db.from()` method provides access to the complete Supabase PostgREST API, supporting all database operations including:
|
|
329
|
-
|
|
330
|
-
- **CRUD Operations**: `insert()`, `select()`, `update()`, `delete()`, `upsert()`
|
|
331
|
-
- **Filtering**: `eq()`, `neq()`, `gt()`, `gte()`, `lt()`, `lte()`, `like()`, `ilike()`, `is()`, `in()`, `contains()`, `containedBy()`, `rangeLt()`, `rangeGt()`, `rangeGte()`, `rangeLte()`, `rangeAdjacent()`, `overlaps()`, `textSearch()`, `match()`, `not()`, `or()`, `filter()`
|
|
332
|
-
- **Modifiers**: `order()`, `limit()`, `range()`, `single()`, `maybe_single()`, `csv()`, `geojson()`, `explain()`
|
|
333
|
-
- **Aggregations**: `count()`, `sum()`, `avg()`, `min()`, `max()`
|
|
334
|
-
- **Advanced Features**: Row Level Security (RLS), real-time subscriptions, stored procedures, and custom functions
|
|
335
|
-
|
|
336
|
-
All operations automatically use your plugin's table prefix for security and isolation.
|
|
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
|
-
|
|
344
|
-
**Example: CRUD Operations**
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
interface StudySession {
|
|
348
|
-
id?: string;
|
|
349
|
-
user_id: string;
|
|
350
|
-
topic: string;
|
|
351
|
-
duration: number;
|
|
352
|
-
completed_at: string;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const StudySessionManager = () => {
|
|
356
|
-
const { db } = useRimori();
|
|
357
|
-
|
|
358
|
-
// Create a new study session
|
|
359
|
-
const createSession = async (session: Omit<StudySession, 'id'>) => {
|
|
360
|
-
const { data, error } = await db
|
|
361
|
-
.from('study_sessions') // Automatically prefixed
|
|
362
|
-
.insert(session)
|
|
363
|
-
.select()
|
|
364
|
-
.single();
|
|
365
|
-
|
|
366
|
-
if (error) throw error;
|
|
367
|
-
return data;
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
// Get user's study sessions
|
|
371
|
-
const getUserSessions = async (userId: string) => {
|
|
372
|
-
const { data, error } = await db
|
|
373
|
-
.from('study_sessions')
|
|
374
|
-
.select('*')
|
|
375
|
-
.eq('user_id', userId)
|
|
376
|
-
.order('completed_at', { ascending: false });
|
|
377
|
-
|
|
378
|
-
if (error) throw error;
|
|
379
|
-
return data;
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
// Update session
|
|
383
|
-
const updateSession = async (id: string, updates: Partial<StudySession>) => {
|
|
384
|
-
const { data, error } = await db
|
|
385
|
-
.from('study_sessions')
|
|
386
|
-
.update(updates)
|
|
387
|
-
.eq('id', id)
|
|
388
|
-
.select()
|
|
389
|
-
.single();
|
|
390
|
-
|
|
391
|
-
if (error) throw error;
|
|
392
|
-
return data;
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
return (
|
|
396
|
-
<div>
|
|
397
|
-
{/* Your component UI */}
|
|
398
|
-
</div>
|
|
399
|
-
);
|
|
400
|
-
};
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
**File Storage Example**
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
const FileManager = () => {
|
|
407
|
-
const { db } = useRimori();
|
|
408
|
-
|
|
409
|
-
const uploadFile = async (file: File) => {
|
|
410
|
-
const fileName = `uploads/${Date.now()}-${file.name}`;
|
|
411
|
-
|
|
412
|
-
const { data, error } = await db.storage
|
|
413
|
-
.from('plugin-files')
|
|
414
|
-
.upload(fileName, file);
|
|
415
|
-
|
|
416
|
-
if (error) throw error;
|
|
417
|
-
return data;
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
const downloadFile = async (filePath: string) => {
|
|
421
|
-
const { data, error } = await db.storage
|
|
422
|
-
.from('plugin-files')
|
|
423
|
-
.download(filePath);
|
|
424
|
-
|
|
425
|
-
if (error) throw error;
|
|
426
|
-
return data;
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
return <div>File Manager UI</div>;
|
|
430
|
-
};
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
## LLM Integration
|
|
434
|
-
|
|
435
|
-
Powerful AI/Language Model capabilities built-in:
|
|
436
|
-
|
|
437
|
-
```typescript
|
|
438
|
-
const { llm } = useRimori();
|
|
439
|
-
|
|
440
|
-
// Text generation
|
|
441
|
-
llm.getText(messages: Message[], tools?: Tool[]): Promise<string>
|
|
442
|
-
|
|
443
|
-
// Streaming text generation
|
|
444
|
-
llm.getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[]): void
|
|
445
|
-
|
|
446
|
-
// Structured object generation
|
|
447
|
-
llm.getObject(request: ObjectRequest): Promise<any>
|
|
448
|
-
|
|
449
|
-
// Text-to-speech
|
|
450
|
-
llm.getVoice(text: string, voice?: string, speed?: number, language?: string): Promise<Blob>
|
|
451
|
-
|
|
452
|
-
// Speech-to-text
|
|
453
|
-
llm.getTextFromVoice(file: Blob): Promise<string>
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
**Example: AI Chat Assistant**
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
import { useChat } from "@rimori/client";
|
|
460
|
-
|
|
461
|
-
const ChatAssistant = () => {
|
|
462
|
-
const { messages, append, isLoading } = useChat();
|
|
463
|
-
const [input, setInput] = useState('');
|
|
464
|
-
|
|
465
|
-
const sendMessage = () => {
|
|
466
|
-
if (!input.trim()) return;
|
|
467
|
-
|
|
468
|
-
append([{
|
|
469
|
-
role: 'user',
|
|
470
|
-
content: input
|
|
471
|
-
}]);
|
|
472
|
-
|
|
473
|
-
setInput('');
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
return (
|
|
477
|
-
<div className="chat-container">
|
|
478
|
-
<div className="messages">
|
|
479
|
-
{messages.map((message, index) => (
|
|
480
|
-
<div key={index} className={`message ${message.role}`}>
|
|
481
|
-
{message.content}
|
|
482
|
-
</div>
|
|
483
|
-
))}
|
|
484
|
-
{isLoading && <div className="message assistant">Thinking...</div>}
|
|
485
|
-
</div>
|
|
486
|
-
|
|
487
|
-
<div className="input-area">
|
|
488
|
-
<input
|
|
489
|
-
value={input}
|
|
490
|
-
onChange={(e) => setInput(e.target.value)}
|
|
491
|
-
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
|
492
|
-
placeholder="Ask anything..."
|
|
493
|
-
/>
|
|
494
|
-
<button onClick={sendMessage} disabled={isLoading}>
|
|
495
|
-
Send
|
|
496
|
-
</button>
|
|
497
|
-
</div>
|
|
498
|
-
</div>
|
|
499
|
-
);
|
|
500
|
-
};
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
**Example: Structured Data Generation**
|
|
504
|
-
|
|
505
|
-
```typescript
|
|
506
|
-
const QuizGenerator = () => {
|
|
507
|
-
const { llm } = useRimori();
|
|
508
|
-
|
|
509
|
-
const generateQuiz = async (topic: string) => {
|
|
510
|
-
const quiz = await llm.getObject({
|
|
511
|
-
schema: {
|
|
512
|
-
type: "object",
|
|
513
|
-
properties: {
|
|
514
|
-
title: { type: "string" },
|
|
515
|
-
questions: {
|
|
516
|
-
type: "array",
|
|
517
|
-
items: {
|
|
518
|
-
type: "object",
|
|
519
|
-
properties: {
|
|
520
|
-
question: { type: "string" },
|
|
521
|
-
options: { type: "array", items: { type: "string" } },
|
|
522
|
-
correctAnswer: { type: "number" },
|
|
523
|
-
explanation: { type: "string" }
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
prompt: `Create a quiz about ${topic} with 5 multiple choice questions.`
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
return quiz;
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
return <div>Quiz Generator UI</div>;
|
|
536
|
-
};
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
**Example: Voice Integration**
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
const VoiceAssistant = () => {
|
|
543
|
-
const { llm } = useRimori();
|
|
544
|
-
|
|
545
|
-
const speakText = async (text: string) => {
|
|
546
|
-
const audioBlob = await llm.getVoice(text, "alloy", 1, "en");
|
|
547
|
-
const audioUrl = URL.createObjectURL(audioBlob);
|
|
548
|
-
const audio = new Audio(audioUrl);
|
|
549
|
-
audio.play();
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
const transcribeAudio = async (audioFile: File) => {
|
|
553
|
-
const transcript = await llm.getTextFromVoice(audioFile);
|
|
554
|
-
return transcript;
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
return <div>Voice Assistant UI</div>;
|
|
558
|
-
};
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Event System
|
|
562
|
-
|
|
563
|
-
Robust inter-plugin communication and platform integration:
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
const { event } = useRimori();
|
|
567
|
-
|
|
568
|
-
// Event methods
|
|
569
|
-
event.emit(topic: string, data?: any, eventId?: number): void
|
|
570
|
-
event.request<T>(topic: string, data?: any): Promise<EventBusMessage<T>>
|
|
571
|
-
event.on<T>(topic: string | string[], callback: EventHandler<T>): string[]
|
|
572
|
-
event.once<T>(topic: string, callback: EventHandler<T>): void
|
|
573
|
-
event.respond<T>(topic: string, data: EventPayload | Function): void
|
|
574
|
-
|
|
575
|
-
// Accomplishments
|
|
576
|
-
event.emitAccomplishment(payload: AccomplishmentPayload): void
|
|
577
|
-
event.onAccomplishment(topic: string, callback: Function): void
|
|
578
|
-
|
|
579
|
-
// Sidebar actions
|
|
580
|
-
event.emitSidebarAction(pluginId: string, actionKey: string, text?: string): void
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
**Example: Plugin Communication**
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
const PluginCommunicator = () => {
|
|
587
|
-
const { event } = useRimori();
|
|
588
|
-
|
|
589
|
-
useEffect(() => {
|
|
590
|
-
// Listen for messages from other plugins
|
|
591
|
-
const unsubscribe = event.on('flashcards.newCard', (message) => {
|
|
592
|
-
console.log('New flashcard created:', message.data);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
// Listen for global events
|
|
596
|
-
event.on('global.userProgress', (message) => {
|
|
597
|
-
console.log('User progress updated:', message.data);
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
return () => {
|
|
601
|
-
// Cleanup subscriptions
|
|
602
|
-
unsubscribe.forEach(id => event.off(id));
|
|
603
|
-
};
|
|
604
|
-
}, []);
|
|
605
|
-
|
|
606
|
-
const shareData = () => {
|
|
607
|
-
// Emit data to other plugins
|
|
608
|
-
event.emit('studyplan.dataUpdate', {
|
|
609
|
-
type: 'session_completed',
|
|
610
|
-
sessionId: '123',
|
|
611
|
-
score: 85
|
|
612
|
-
});
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
const requestData = async () => {
|
|
616
|
-
// Request data from another plugin
|
|
617
|
-
const response = await event.request('flashcards.getStats', {
|
|
618
|
-
timeframe: 'week'
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
console.log('Flashcard stats:', response.data);
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
return (
|
|
625
|
-
<div>
|
|
626
|
-
<button onClick={shareData}>Share Progress</button>
|
|
627
|
-
<button onClick={requestData}>Get Flashcard Stats</button>
|
|
628
|
-
</div>
|
|
629
|
-
);
|
|
630
|
-
};
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
**Example: Accomplishment System**
|
|
634
|
-
|
|
635
|
-
```typescript
|
|
636
|
-
const AccomplishmentTracker = () => {
|
|
637
|
-
const { event } = useRimori();
|
|
638
|
-
|
|
639
|
-
const trackAccomplishment = () => {
|
|
640
|
-
event.emitAccomplishment({
|
|
641
|
-
type: 'study_milestone',
|
|
642
|
-
title: 'Study Streak',
|
|
643
|
-
description: 'Completed 7 days of studying',
|
|
644
|
-
points: 100,
|
|
645
|
-
metadata: {
|
|
646
|
-
streakDays: 7,
|
|
647
|
-
subject: 'Spanish'
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
useEffect(() => {
|
|
653
|
-
// Listen for accomplishments from this plugin
|
|
654
|
-
event.onAccomplishment('study_milestone', (accomplishment) => {
|
|
655
|
-
console.log('New accomplishment:', accomplishment);
|
|
656
|
-
// Show notification, update UI, etc.
|
|
657
|
-
});
|
|
658
|
-
}, []);
|
|
659
|
-
|
|
660
|
-
return <div>Accomplishment Tracker UI</div>;
|
|
661
|
-
};
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
**Example: Sidebar Integration**
|
|
665
|
-
|
|
666
|
-
```typescript
|
|
667
|
-
const SidebarIntegration = () => {
|
|
668
|
-
const { event } = useRimori();
|
|
669
|
-
|
|
670
|
-
const openTranslator = (text: string) => {
|
|
671
|
-
// Trigger translator plugin in sidebar
|
|
672
|
-
event.emitSidebarAction('translator', 'translate', text);
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
const openFlashcards = () => {
|
|
676
|
-
// Open flashcards plugin
|
|
677
|
-
event.emitSidebarAction('flashcards', 'review');
|
|
678
|
-
};
|
|
679
|
-
|
|
680
|
-
return (
|
|
681
|
-
<div>
|
|
682
|
-
<button onClick={() => openTranslator('Hello world')}>
|
|
683
|
-
Translate "Hello world"
|
|
684
|
-
</button>
|
|
685
|
-
<button onClick={openFlashcards}>
|
|
686
|
-
Review Flashcards
|
|
687
|
-
</button>
|
|
688
|
-
</div>
|
|
689
|
-
);
|
|
690
|
-
};
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
## Community Features
|
|
694
|
-
|
|
695
|
-
Share and discover content created by other users:
|
|
696
|
-
|
|
697
|
-
```typescript
|
|
698
|
-
const { community } = useRimori();
|
|
699
|
-
|
|
700
|
-
// Shared content methods
|
|
701
|
-
community.sharedContent.get<T>(contentType: string, id: string): Promise<BasicAssignment<T>>
|
|
702
|
-
community.sharedContent.getList<T>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<BasicAssignment<T>[]>
|
|
703
|
-
community.sharedContent.getNew<T>(contentType: string, instructions: SharedContentObjectRequest, filter?: SharedContentFilter, privateTopic?: boolean): Promise<BasicAssignment<T>>
|
|
704
|
-
community.sharedContent.create<T>(content: SharedContent<T>): Promise<BasicAssignment<T>>
|
|
705
|
-
community.sharedContent.update<T>(id: string, content: Partial<SharedContent<T>>): Promise<BasicAssignment<T>>
|
|
706
|
-
community.sharedContent.remove(id: string): Promise<BasicAssignment<any>>
|
|
707
|
-
community.sharedContent.complete(contentType: string, assignmentId: string): Promise<void>
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
**Example: Exercise Sharing Platform**
|
|
711
|
-
|
|
712
|
-
```typescript
|
|
713
|
-
interface Exercise {
|
|
714
|
-
title: string;
|
|
715
|
-
description: string;
|
|
716
|
-
difficulty: 'beginner' | 'intermediate' | 'advanced';
|
|
717
|
-
questions: Array<{
|
|
718
|
-
question: string;
|
|
719
|
-
answer: string;
|
|
720
|
-
hints?: string[];
|
|
721
|
-
}>;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const ExerciseManager = () => {
|
|
725
|
-
const { community } = useRimori();
|
|
726
|
-
const [exercises, setExercises] = useState<BasicAssignment<Exercise>[]>([]);
|
|
727
|
-
|
|
728
|
-
// Load community exercises
|
|
729
|
-
const loadExercises = async () => {
|
|
730
|
-
const exerciseList = await community.sharedContent.getList<Exercise>(
|
|
731
|
-
'grammar_exercises',
|
|
732
|
-
{ column: 'difficulty', value: 'beginner' },
|
|
733
|
-
10
|
|
734
|
-
);
|
|
735
|
-
setExercises(exerciseList);
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
// Create new exercise
|
|
739
|
-
const createExercise = async (exercise: Exercise) => {
|
|
740
|
-
const newExercise = await community.sharedContent.create({
|
|
741
|
-
content_type: 'grammar_exercises',
|
|
742
|
-
content: exercise,
|
|
743
|
-
metadata: {
|
|
744
|
-
difficulty: exercise.difficulty,
|
|
745
|
-
questionCount: exercise.questions.length
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
return newExercise;
|
|
750
|
-
};
|
|
751
|
-
|
|
752
|
-
// Generate AI exercise
|
|
753
|
-
const generateExercise = async (topic: string) => {
|
|
754
|
-
const aiExercise = await community.sharedContent.getNew<Exercise>(
|
|
755
|
-
'grammar_exercises',
|
|
756
|
-
{
|
|
757
|
-
prompt: `Create a grammar exercise about ${topic}`,
|
|
758
|
-
schema: {
|
|
759
|
-
type: "object",
|
|
760
|
-
properties: {
|
|
761
|
-
title: { type: "string" },
|
|
762
|
-
description: { type: "string" },
|
|
763
|
-
difficulty: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
|
|
764
|
-
questions: {
|
|
765
|
-
type: "array",
|
|
766
|
-
items: {
|
|
767
|
-
type: "object",
|
|
768
|
-
properties: {
|
|
769
|
-
question: { type: "string" },
|
|
770
|
-
answer: { type: "string" },
|
|
771
|
-
hints: { type: "array", items: { type: "string" } }
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
},
|
|
778
|
-
{ column: 'difficulty', value: 'beginner' }
|
|
779
|
-
);
|
|
780
|
-
|
|
781
|
-
return aiExercise;
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
// Complete exercise
|
|
785
|
-
const completeExercise = async (exerciseId: string) => {
|
|
786
|
-
await community.sharedContent.complete('grammar_exercises', exerciseId);
|
|
787
|
-
// Exercise is now marked as completed for the user
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
return (
|
|
791
|
-
<div>
|
|
792
|
-
<button onClick={loadExercises}>Load Exercises</button>
|
|
793
|
-
<button onClick={() => generateExercise('present tense')}>
|
|
794
|
-
Generate Present Tense Exercise
|
|
795
|
-
</button>
|
|
796
|
-
{/* Exercise list UI */}
|
|
797
|
-
</div>
|
|
798
|
-
);
|
|
799
|
-
};
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
## Components
|
|
803
|
-
|
|
804
|
-
Pre-built React components for common functionality:
|
|
805
|
-
|
|
806
|
-
### MarkdownEditor
|
|
807
|
-
|
|
808
|
-
Rich text editor with markdown support:
|
|
809
|
-
|
|
810
|
-
```typescript
|
|
811
|
-
import { MarkdownEditor } from "@rimori/client";
|
|
812
|
-
|
|
813
|
-
const EditorExample = () => {
|
|
814
|
-
const [content, setContent] = useState('');
|
|
815
|
-
|
|
816
|
-
return (
|
|
817
|
-
<MarkdownEditor
|
|
818
|
-
value={content}
|
|
819
|
-
onChange={setContent}
|
|
820
|
-
placeholder="Start writing..."
|
|
821
|
-
/>
|
|
822
|
-
);
|
|
823
|
-
};
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
### CRUDModal
|
|
827
|
-
|
|
828
|
-
Modal component for create/update operations:
|
|
829
|
-
|
|
830
|
-
```typescript
|
|
831
|
-
import { CRUDModal } from "@rimori/client";
|
|
832
|
-
|
|
833
|
-
const DataManager = () => {
|
|
834
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
835
|
-
const [editItem, setEditItem] = useState(null);
|
|
836
|
-
|
|
837
|
-
return (
|
|
838
|
-
<CRUDModal
|
|
839
|
-
isOpen={isOpen}
|
|
840
|
-
onClose={() => setIsOpen(false)}
|
|
841
|
-
title={editItem ? "Edit Item" : "Create Item"}
|
|
842
|
-
onSave={(data) => {
|
|
843
|
-
// Handle save logic
|
|
844
|
-
console.log('Saving:', data);
|
|
845
|
-
setIsOpen(false);
|
|
846
|
-
}}
|
|
847
|
-
initialData={editItem}
|
|
848
|
-
>
|
|
849
|
-
{/* Your form content */}
|
|
850
|
-
<input placeholder="Item name" />
|
|
851
|
-
<textarea placeholder="Description" />
|
|
852
|
-
</CRUDModal>
|
|
853
|
-
);
|
|
854
|
-
};
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
### Spinner
|
|
858
|
-
|
|
859
|
-
Loading indicator component:
|
|
860
|
-
|
|
861
|
-
```typescript
|
|
862
|
-
import { Spinner } from "@rimori/client";
|
|
863
|
-
|
|
864
|
-
const LoadingExample = () => {
|
|
865
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
866
|
-
|
|
867
|
-
if (isLoading) {
|
|
868
|
-
return <Spinner size="large" />;
|
|
869
|
-
}
|
|
117
|
+
Access metadata and settings through `client.plugin`:
|
|
870
118
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
119
|
+
- `plugin.pluginId` – current plugin identifier.
|
|
120
|
+
- `plugin.getSettings(defaults)` / `plugin.setSettings(settings)` – persist configuration.
|
|
121
|
+
- `plugin.getPluginInfo()` – read active/installed plugin information.
|
|
122
|
+
- `plugin.getUserInfo()` – obtain user profile details (language, name, guild, etc.).
|
|
123
|
+
- `plugin.getTranslator()` – lazily initialize the translator for manual i18n.
|
|
876
124
|
|
|
877
|
-
|
|
125
|
+
### Database Access
|
|
878
126
|
|
|
879
|
-
|
|
880
|
-
import { PlayButton } from "@rimori/client";
|
|
127
|
+
`client.db` wraps the Supabase client that is scoped to your plugin tables:
|
|
881
128
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
onPause={() => console.log('Audio paused')}
|
|
888
|
-
/>
|
|
889
|
-
);
|
|
890
|
-
};
|
|
129
|
+
```ts
|
|
130
|
+
const { data, error } = await client.db
|
|
131
|
+
.from("study_sessions")
|
|
132
|
+
.select("*")
|
|
133
|
+
.order("completed_at", { ascending: false });
|
|
891
134
|
```
|
|
892
135
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
```typescript
|
|
896
|
-
import { Avatar, Assistant } from "@rimori/client";
|
|
897
|
-
|
|
898
|
-
const AIInterface = () => {
|
|
899
|
-
return (
|
|
900
|
-
<div>
|
|
901
|
-
<Avatar
|
|
902
|
-
name="AI Assistant"
|
|
903
|
-
status="online"
|
|
904
|
-
size="large"
|
|
905
|
-
/>
|
|
906
|
-
|
|
907
|
-
<Assistant
|
|
908
|
-
onMessage={(message) => console.log('AI message:', message)}
|
|
909
|
-
placeholder="Ask the AI assistant..."
|
|
910
|
-
/>
|
|
911
|
-
</div>
|
|
912
|
-
);
|
|
913
|
-
};
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
## Hooks
|
|
917
|
-
|
|
918
|
-
### useChat
|
|
919
|
-
|
|
920
|
-
Manage AI chat conversations:
|
|
921
|
-
|
|
922
|
-
```typescript
|
|
923
|
-
import { useChat } from "@rimori/client";
|
|
924
|
-
|
|
925
|
-
const ChatExample = () => {
|
|
926
|
-
const { messages, append, isLoading, setMessages } = useChat([
|
|
927
|
-
// Optional tools for the AI
|
|
928
|
-
{
|
|
929
|
-
name: "get_weather",
|
|
930
|
-
description: "Get current weather",
|
|
931
|
-
parameters: {
|
|
932
|
-
type: "object",
|
|
933
|
-
properties: {
|
|
934
|
-
location: { type: "string" }
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
]);
|
|
939
|
-
|
|
940
|
-
const sendMessage = (content: string) => {
|
|
941
|
-
append([{ role: 'user', content }]);
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
return (
|
|
945
|
-
<div>
|
|
946
|
-
{messages.map((msg, index) => (
|
|
947
|
-
<div key={index}>{msg.content}</div>
|
|
948
|
-
))}
|
|
949
|
-
{isLoading && <div>AI is typing...</div>}
|
|
950
|
-
</div>
|
|
951
|
-
);
|
|
952
|
-
};
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
### useTranslation
|
|
956
|
-
|
|
957
|
-
Internationalization (i18n) support built on i18next:
|
|
958
|
-
|
|
959
|
-
```typescript
|
|
960
|
-
import { useTranslation } from "@rimori/client";
|
|
961
|
-
|
|
962
|
-
const TranslatedComponent = () => {
|
|
963
|
-
const { t, ready } = useTranslation();
|
|
964
|
-
|
|
965
|
-
if (!ready) {
|
|
966
|
-
return <div>Loading translations...</div>;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
return (
|
|
970
|
-
<div>
|
|
971
|
-
<h1>{t('discussion.title')}</h1>
|
|
972
|
-
<p>{t('discussion.whatToTalkAbout')}</p>
|
|
973
|
-
</div>
|
|
974
|
-
);
|
|
975
|
-
};
|
|
976
|
-
```
|
|
977
|
-
|
|
978
|
-
## Translation Feature
|
|
979
|
-
|
|
980
|
-
Rimori includes a comprehensive internationalization (i18n) system built on i18next that allows plugins to support multiple languages with minimal developer effort.
|
|
981
|
-
|
|
982
|
-
### How it works
|
|
136
|
+
Helpers:
|
|
983
137
|
|
|
984
|
-
-
|
|
985
|
-
-
|
|
986
|
-
-
|
|
987
|
-
1. Setting your user language to a non-English locale (e.g., German)
|
|
988
|
-
2. Creating a local translation file with "local-" prefix (e.g., `local-de.json`) in the `public/locales/` directory
|
|
989
|
-
3. The translator will automatically use the local translation file in development mode
|
|
990
|
-
- **Manual Translations**: If developers want to manually translate files, they should place the language file manually in the `public/locales/` folder with the language code as filename (e.g., `de.json`, `fr.json`)
|
|
138
|
+
- `db.tablePrefix` – plugin-specific prefix applied to all tables.
|
|
139
|
+
- `db.getTableName("notes")` – resolve the fully qualified table name.
|
|
140
|
+
- Supabase query builder methods (`insert`, `update`, `delete`, `eq`, `limit`, etc.) are available out of the box.
|
|
991
141
|
|
|
992
|
-
###
|
|
142
|
+
### AI & Voice
|
|
993
143
|
|
|
994
|
-
|
|
144
|
+
The `client.ai` controller surfaces AI capabilities:
|
|
995
145
|
|
|
996
|
-
|
|
997
|
-
|
|
146
|
+
- `getText(messages, tools?)` – chat completion (string result).
|
|
147
|
+
- `getSteamedText(messages, onMessage, tools?)` – streamed responses.
|
|
148
|
+
- `getObject(request)` – structured JSON generation.
|
|
149
|
+
- `getVoice(text, voice?, speed?, language?)` – text-to-speech (returns `Blob`).
|
|
150
|
+
- `getTextFromVoice(file)` – speech-to-text transcription.
|
|
998
151
|
|
|
999
|
-
|
|
1000
|
-
const { t, ready } = useTranslation();
|
|
1001
|
-
|
|
1002
|
-
if (!ready) {
|
|
1003
|
-
return <div>Loading translations...</div>;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return (
|
|
1007
|
-
<div>
|
|
1008
|
-
<h1>{t('discussion.title')}</h1>
|
|
1009
|
-
<p>{t('discussion.whatToTalkAbout')}</p>
|
|
1010
|
-
</div>
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
```
|
|
152
|
+
Use `client.runtime.fetchBackend` for authenticated calls to Rimori-managed HTTP endpoints.
|
|
1014
153
|
|
|
1015
|
-
|
|
154
|
+
### Event Bus & Actions
|
|
1016
155
|
|
|
1017
|
-
|
|
1018
|
-
import { useRimori } from "@rimori/client";
|
|
156
|
+
`client.event` lets you collaborate with Rimori and other plugins:
|
|
1019
157
|
|
|
1020
|
-
|
|
1021
|
-
|
|
158
|
+
- `emit(topic, data?)`, `request(topic, data?)` – publish and request data.
|
|
159
|
+
- `on(topic, handler)` / `once(topic, handler)` / `respond(topic, handler)` – subscribe and reply (each call returns an object with `off()` for cleanup).
|
|
160
|
+
- `emitAccomplishment(payload)` / `onAccomplishment(topic, handler)` – report learning milestones.
|
|
161
|
+
- `emitSidebarAction(pluginId, actionKey, text?)` – trigger sidebar plugins.
|
|
162
|
+
- `onMainPanelAction(handler, actionsToListen?)` – react to dashboard actions.
|
|
163
|
+
- `client.navigation.toDashboard()` – navigate the user back to Rimori.
|
|
1022
164
|
|
|
1023
|
-
|
|
1024
|
-
```
|
|
165
|
+
### Community Content
|
|
1025
166
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
- **Location**: `public/locales/`
|
|
1029
|
-
- **Production Files**: Must be named `{language}.json` (e.g., `en.json`, `de.json`, `fr.json`)
|
|
1030
|
-
- **Local Development Files**: Must be named `local-{language}.json` (e.g., `local-de.json`, `local-fr.json`)
|
|
1031
|
-
- **Format**: Standard JSON with nested objects for organization
|
|
1032
|
-
- **English Requirement**: `en.json` is required as the base language
|
|
1033
|
-
- **Release Process**: Files starting with "local-" are ignored during the release process
|
|
1034
|
-
|
|
1035
|
-
Example translation file structure:
|
|
1036
|
-
|
|
1037
|
-
```json
|
|
1038
|
-
{
|
|
1039
|
-
"discussion": {
|
|
1040
|
-
"title": "Discussion",
|
|
1041
|
-
"whatToTalkAbout": "What do you want to talk about?",
|
|
1042
|
-
"topics": {
|
|
1043
|
-
"everyday": {
|
|
1044
|
-
"title": "Everyday Conversations",
|
|
1045
|
-
"description": "Ordering coffee, asking for directions, etc."
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
```
|
|
167
|
+
`client.community.sharedContent` exposes helpers to share or consume content:
|
|
1051
168
|
|
|
1052
|
-
|
|
169
|
+
- `get(contentType, id)`
|
|
170
|
+
- `getList(contentType, filter?, limit?)`
|
|
171
|
+
- `getNew(contentType, instructions, filter?, options?)`
|
|
172
|
+
- `create(payload)`
|
|
173
|
+
- `update(id, payload)`
|
|
174
|
+
- `remove(id)`
|
|
175
|
+
- `complete(contentType, assignmentId)`
|
|
1053
176
|
|
|
1054
|
-
|
|
1055
|
-
- Variable interpolation: `{{name}}`
|
|
1056
|
-
- Pluralization
|
|
1057
|
-
- Fallback mechanisms
|
|
1058
|
-
- **Automatic Fallback**: If a translation is missing, it falls back to English
|
|
1059
|
-
- **Development Mode**: Local translation files are prioritized in development
|
|
1060
|
-
- **Production Ready**: Automatic translation generation for production releases
|
|
177
|
+
The controller handles topic generation, metadata, and completion tracking automatically.
|
|
1061
178
|
|
|
1062
|
-
###
|
|
179
|
+
### Workers & Standalone Development
|
|
1063
180
|
|
|
1064
|
-
-
|
|
1065
|
-
-
|
|
1066
|
-
-
|
|
1067
|
-
- Local development files must be named `local-{language}.json` and placed in `public/locales/`
|
|
1068
|
-
- English (`en.json`) is required as the base language
|
|
1069
|
-
- Local files (prefixed with "local-") are ignored during the release process
|
|
181
|
+
- `setupWorker()` wires the Rimori event bus into worker contexts.
|
|
182
|
+
- `StandaloneClient.getInstance()` signs in against Rimori when your plugin runs outside the platform (e.g., local development).
|
|
183
|
+
- `client.getQueryParam(key)` reads values provided by Rimori through the sandbox handshake (such as `applicationMode` or theme information).
|
|
1070
184
|
|
|
1071
185
|
## Utilities
|
|
1072
186
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
Convert between different difficulty representations:
|
|
187
|
+
Import additional helpers as needed:
|
|
1076
188
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
const difficultyText = difficultyConverter.toString(3); // Returns: 'advanced'
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
### PluginUtils
|
|
1085
|
-
|
|
1086
|
-
Various utility functions:
|
|
1087
|
-
|
|
1088
|
-
```typescript
|
|
1089
|
-
import { PluginUtils } from '@rimori/client';
|
|
1090
|
-
|
|
1091
|
-
// Utility functions for common plugin operations
|
|
1092
|
-
const utils = PluginUtils.getInstance();
|
|
1093
|
-
// Access various helper methods
|
|
1094
|
-
```
|
|
1095
|
-
|
|
1096
|
-
### Language Utilities
|
|
1097
|
-
|
|
1098
|
-
Language detection and processing:
|
|
1099
|
-
|
|
1100
|
-
```typescript
|
|
1101
|
-
import { Language } from '@rimori/client';
|
|
1102
|
-
|
|
1103
|
-
// Language-related utility functions
|
|
1104
|
-
const languageCode = Language.detectLanguage(text);
|
|
1105
|
-
const isSupported = Language.isSupported('es');
|
|
1106
|
-
```
|
|
189
|
+
- `AudioController` – high-level audio playback/recording utilities for non-React environments.
|
|
190
|
+
- `Translator` – encapsulated i18next integration for manual translation flows.
|
|
191
|
+
- `difficultyConverter` – convert between textual and numeric difficulty levels.
|
|
192
|
+
- Type definitions for AI messages, shared content, triggers, accomplishments, and more.
|
|
1107
193
|
|
|
1108
194
|
## TypeScript Support
|
|
1109
195
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
```typescript
|
|
1113
|
-
import type {
|
|
1114
|
-
MainPanelAction,
|
|
1115
|
-
Message,
|
|
1116
|
-
Tool,
|
|
1117
|
-
EventPayload,
|
|
1118
|
-
AccomplishmentPayload,
|
|
1119
|
-
SharedContent,
|
|
1120
|
-
BasicAssignment,
|
|
1121
|
-
UserInfo,
|
|
1122
|
-
} from '@rimori/client';
|
|
1123
|
-
|
|
1124
|
-
// All interfaces and types are exported for use in your plugin
|
|
1125
|
-
interface MyPluginData extends SharedContent<any> {
|
|
1126
|
-
// Your custom properties
|
|
1127
|
-
}
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
The SharedContent has this type definition:
|
|
196
|
+
All exports are fully typed. You can import the type definitions directly:
|
|
1131
197
|
|
|
198
|
+
```ts
|
|
199
|
+
import type { Message, Tool, SharedContent, MacroAccomplishmentPayload } from "@rimori/client";
|
|
1132
200
|
```
|
|
1133
|
-
export interface SharedContent<T> {
|
|
1134
|
-
/** The type/category of the content (e.g. 'grammar_exercises', 'flashcards', etc.) */
|
|
1135
|
-
contentType: string;
|
|
1136
201
|
|
|
1137
|
-
|
|
1138
|
-
topic: string;
|
|
202
|
+
The generated declaration files cover every controller and helper to keep plugins strictly typed.
|
|
1139
203
|
|
|
1140
|
-
|
|
1141
|
-
keywords: string[];
|
|
204
|
+
## Example Integration
|
|
1142
205
|
|
|
1143
|
-
|
|
1144
|
-
data: T;
|
|
206
|
+
React users should install `@rimori/react-client` and wrap their app:
|
|
1145
207
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
```
|
|
208
|
+
```tsx
|
|
209
|
+
import { PluginProvider, useRimori, useChat } from "@rimori/react-client";
|
|
1150
210
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
### Complete Plugin Example
|
|
1154
|
-
|
|
1155
|
-
```typescript
|
|
1156
|
-
import React, { useState, useEffect } from 'react';
|
|
1157
|
-
import {
|
|
1158
|
-
PluginProvider,
|
|
1159
|
-
usePlugin,
|
|
1160
|
-
MarkdownEditor,
|
|
1161
|
-
Spinner,
|
|
1162
|
-
useChat
|
|
1163
|
-
} from '@rimori/client';
|
|
1164
|
-
import { HashRouter, Route, Routes } from 'react-router-dom';
|
|
1165
|
-
|
|
1166
|
-
const StudyNotesPlugin = () => {
|
|
1167
|
-
const { db, llm, plugin, community } = useRimori();
|
|
1168
|
-
const [notes, setNotes] = useState([]);
|
|
1169
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
211
|
+
function Dashboard() {
|
|
212
|
+
const client = useRimori();
|
|
1170
213
|
const { messages, append } = useChat();
|
|
1171
214
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const loadNotes = async () => {
|
|
1177
|
-
try {
|
|
1178
|
-
const { data } = await db.from('notes').select('*').order('created_at', { ascending: false });
|
|
1179
|
-
setNotes(data || []);
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
console.error('Error loading notes:', error);
|
|
1182
|
-
} finally {
|
|
1183
|
-
setIsLoading(false);
|
|
1184
|
-
}
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
const saveNote = async (content: string) => {
|
|
1188
|
-
const { data } = await db.from('notes').insert({
|
|
1189
|
-
content,
|
|
1190
|
-
created_at: new Date().toISOString()
|
|
1191
|
-
}).select().single();
|
|
1192
|
-
|
|
1193
|
-
setNotes([data, ...notes]);
|
|
1194
|
-
|
|
1195
|
-
// Share with community
|
|
1196
|
-
await community.sharedContent.create({
|
|
1197
|
-
content_type: 'study_notes',
|
|
1198
|
-
content: { text: content },
|
|
1199
|
-
metadata: { wordCount: content.length }
|
|
1200
|
-
});
|
|
1201
|
-
};
|
|
1202
|
-
|
|
1203
|
-
const generateSummary = async (noteContent: string) => {
|
|
1204
|
-
const summary = await llm.getText([
|
|
1205
|
-
{ role: 'user', content: `Summarize this study note: ${noteContent}` }
|
|
1206
|
-
]);
|
|
1207
|
-
|
|
1208
|
-
return summary;
|
|
1209
|
-
};
|
|
1210
|
-
|
|
1211
|
-
if (isLoading) return <Spinner size="large" />;
|
|
215
|
+
// interact with the core API through the client instance
|
|
216
|
+
// e.g. client.db.from("notes")...
|
|
217
|
+
}
|
|
1212
218
|
|
|
219
|
+
export function App() {
|
|
1213
220
|
return (
|
|
1214
|
-
<
|
|
1215
|
-
<
|
|
1216
|
-
|
|
1217
|
-
<div className="notes-grid">
|
|
1218
|
-
{notes.map(note => (
|
|
1219
|
-
<div key={note.id} className="note-card">
|
|
1220
|
-
<MarkdownEditor
|
|
1221
|
-
value={note.content}
|
|
1222
|
-
onChange={(content) => {/* Update logic */}}
|
|
1223
|
-
/>
|
|
1224
|
-
<button onClick={() => generateSummary(note.content)}>
|
|
1225
|
-
Generate Summary
|
|
1226
|
-
</button>
|
|
1227
|
-
</div>
|
|
1228
|
-
))}
|
|
1229
|
-
</div>
|
|
1230
|
-
|
|
1231
|
-
<div className="ai-chat">
|
|
1232
|
-
<h2>Study Assistant</h2>
|
|
1233
|
-
{messages.map((msg, index) => (
|
|
1234
|
-
<div key={index} className={`message ${msg.role}`}>
|
|
1235
|
-
{msg.content}
|
|
1236
|
-
</div>
|
|
1237
|
-
))}
|
|
1238
|
-
<button onClick={() => append([{ role: 'user', content: 'Help me study' }])}>
|
|
1239
|
-
Get Study Help
|
|
1240
|
-
</button>
|
|
1241
|
-
</div>
|
|
1242
|
-
</div>
|
|
221
|
+
<PluginProvider pluginId="your-plugin-id">
|
|
222
|
+
<Dashboard />
|
|
223
|
+
</PluginProvider>
|
|
1243
224
|
);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
const App = () => (
|
|
1247
|
-
<PluginProvider pluginId="study-notes-plugin">
|
|
1248
|
-
<HashRouter>
|
|
1249
|
-
<Routes>
|
|
1250
|
-
<Route path="/" element={<StudyNotesPlugin />} />
|
|
1251
|
-
</Routes>
|
|
1252
|
-
</HashRouter>
|
|
1253
|
-
</PluginProvider>
|
|
1254
|
-
);
|
|
1255
|
-
|
|
1256
|
-
export default App;
|
|
225
|
+
}
|
|
1257
226
|
```
|
|
1258
227
|
|
|
1259
|
-
|
|
228
|
+
Non-React projects can interact with the same client instance directly via the examples in the sections above.
|
|
229
|
+
|
|
230
|
+
## Troubleshooting
|
|
1260
231
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
4. **Event Cleanup**: Always unsubscribe from events in useEffect cleanup
|
|
1265
|
-
5. **Responsive Design**: Use TailwindCSS classes for responsive layouts
|
|
232
|
+
- **`ReferenceError: process is not defined` in workers** – ensure worker bundles only import from `@rimori/client`. Packages that reference `process.env` are not compatible with Rimori workers.
|
|
233
|
+
- **Missing plugin ID or token** – re-run `rimori-init` to regenerate configuration and authentication secrets.
|
|
234
|
+
- **Event bus listeners firing twice** – store the listener returned by `event.on` and call `listener.off()` during cleanup (React users get this cleanup inside the hooks provided by `@rimori/react-client`).
|