@rimori/react-client 0.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/.prettierignore +35 -0
- package/LICENSE +201 -0
- package/README copy.md +1216 -0
- package/README.md +1 -0
- package/dist/components/MarkdownEditor.d.ts +8 -0
- package/dist/components/MarkdownEditor.js +48 -0
- package/dist/components/Spinner.d.ts +8 -0
- package/dist/components/Spinner.js +4 -0
- package/dist/components/ai/Assistant.d.ts +9 -0
- package/dist/components/ai/Assistant.js +58 -0
- package/dist/components/ai/Avatar.d.ts +14 -0
- package/dist/components/ai/Avatar.js +59 -0
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
- package/dist/components/ai/utils.d.ts +6 -0
- package/dist/components/ai/utils.js +13 -0
- package/dist/components/audio/Playbutton.d.ts +15 -0
- package/dist/components/audio/Playbutton.js +80 -0
- package/dist/components/components/ContextMenu.d.ts +10 -0
- package/dist/components/components/ContextMenu.js +135 -0
- package/dist/hooks/I18nHooks.d.ts +11 -0
- package/dist/hooks/I18nHooks.js +25 -0
- package/dist/hooks/UseChatHook.d.ts +10 -0
- package/dist/hooks/UseChatHook.js +29 -0
- package/dist/providers/PluginProvider.d.ts +11 -0
- package/dist/providers/PluginProvider.js +142 -0
- package/dist/react-client/plugin/ThemeSetter.d.ts +2 -0
- package/dist/react-client/plugin/ThemeSetter.js +19 -0
- package/dist/react-client/src/components/ContextMenu.d.ts +10 -0
- package/dist/react-client/src/components/ContextMenu.js +135 -0
- package/dist/react-client/src/components/MarkdownEditor.d.ts +8 -0
- package/dist/react-client/src/components/MarkdownEditor.js +48 -0
- package/dist/react-client/src/components/Spinner.d.ts +8 -0
- package/dist/react-client/src/components/Spinner.js +4 -0
- package/dist/react-client/src/components/ai/Assistant.d.ts +9 -0
- package/dist/react-client/src/components/ai/Assistant.js +58 -0
- package/dist/react-client/src/components/ai/Avatar.d.ts +14 -0
- package/dist/react-client/src/components/ai/Avatar.js +59 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.d.ts +7 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.js +37 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +8 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +79 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +19 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.js +91 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.d.ts +27 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.js +185 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +11 -0
- package/dist/react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.js +95 -0
- package/dist/react-client/src/components/ai/utils.d.ts +6 -0
- package/dist/react-client/src/components/ai/utils.js +13 -0
- package/dist/react-client/src/components/audio/Playbutton.d.ts +15 -0
- package/dist/react-client/src/components/audio/Playbutton.js +82 -0
- package/dist/react-client/src/components/components/ContextMenu.d.ts +10 -0
- package/dist/react-client/src/components/components/ContextMenu.js +135 -0
- package/dist/react-client/src/hooks/I18nHooks.d.ts +11 -0
- package/dist/react-client/src/hooks/I18nHooks.js +25 -0
- package/dist/react-client/src/hooks/UseChatHook.d.ts +10 -0
- package/dist/react-client/src/hooks/UseChatHook.js +29 -0
- package/dist/react-client/src/plugin/ThemeSetter.d.ts +2 -0
- package/dist/react-client/src/plugin/ThemeSetter.js +19 -0
- package/dist/react-client/src/providers/PluginProvider.d.ts +12 -0
- package/dist/react-client/src/providers/PluginProvider.js +142 -0
- package/dist/react-client/src/utils/FullscreenUtils.d.ts +2 -0
- package/dist/react-client/src/utils/FullscreenUtils.js +23 -0
- package/dist/react-client/src/utils/PluginUtils.d.ts +2 -0
- package/dist/react-client/src/utils/PluginUtils.js +23 -0
- package/dist/rimori-client/src/cli/types/DatabaseTypes.d.ts +103 -0
- package/dist/rimori-client/src/cli/types/DatabaseTypes.js +2 -0
- package/dist/rimori-client/src/controller/AIController.d.ts +15 -0
- package/dist/rimori-client/src/controller/AIController.js +255 -0
- package/dist/rimori-client/src/controller/AccomplishmentController.d.ts +38 -0
- package/dist/rimori-client/src/controller/AccomplishmentController.js +112 -0
- package/dist/rimori-client/src/controller/AudioController.d.ts +37 -0
- package/dist/rimori-client/src/controller/AudioController.js +68 -0
- package/dist/rimori-client/src/controller/ExerciseController.d.ts +54 -0
- package/dist/rimori-client/src/controller/ExerciseController.js +74 -0
- package/dist/rimori-client/src/controller/ObjectController.d.ts +42 -0
- package/dist/rimori-client/src/controller/ObjectController.js +76 -0
- package/dist/rimori-client/src/controller/SettingsController.d.ts +79 -0
- package/dist/rimori-client/src/controller/SettingsController.js +118 -0
- package/dist/rimori-client/src/controller/SharedContentController.d.ts +106 -0
- package/dist/rimori-client/src/controller/SharedContentController.js +285 -0
- package/dist/rimori-client/src/controller/TranslationController.d.ts +38 -0
- package/dist/rimori-client/src/controller/TranslationController.js +106 -0
- package/dist/rimori-client/src/controller/VoiceController.d.ts +9 -0
- package/dist/rimori-client/src/controller/VoiceController.js +37 -0
- package/dist/rimori-client/src/fromRimori/EventBus.d.ts +101 -0
- package/dist/rimori-client/src/fromRimori/EventBus.js +263 -0
- package/dist/rimori-client/src/fromRimori/PluginTypes.d.ts +174 -0
- package/dist/rimori-client/src/fromRimori/PluginTypes.js +1 -0
- package/dist/rimori-client/src/index.d.ts +11 -0
- package/dist/rimori-client/src/index.js +10 -0
- package/dist/rimori-client/src/plugin/CommunicationHandler.d.ts +48 -0
- package/dist/rimori-client/src/plugin/CommunicationHandler.js +234 -0
- package/dist/rimori-client/src/plugin/Logger.d.ts +73 -0
- package/dist/rimori-client/src/plugin/Logger.js +308 -0
- package/dist/rimori-client/src/plugin/RimoriClient.d.ts +258 -0
- package/dist/rimori-client/src/plugin/RimoriClient.js +375 -0
- package/dist/rimori-client/src/plugin/StandaloneClient.d.ts +17 -0
- package/dist/rimori-client/src/plugin/StandaloneClient.js +115 -0
- package/dist/rimori-client/src/utils/difficultyConverter.d.ts +4 -0
- package/dist/rimori-client/src/utils/difficultyConverter.js +10 -0
- package/dist/rimori-client/src/utils/endpoint.d.ts +2 -0
- package/dist/rimori-client/src/utils/endpoint.js +2 -0
- package/dist/style.css +110 -0
- package/dist/style.css.map +1 -0
- package/dist/utils/PluginUtils.d.ts +2 -0
- package/dist/utils/PluginUtils.js +23 -0
- package/eslint.config.js +53 -0
- package/index.ts +6 -0
- package/package.json +47 -0
- package/prettier.config.js +8 -0
- package/src/components/ContextMenu.tsx +177 -0
- package/src/components/MarkdownEditor.tsx +144 -0
- package/src/components/Spinner.tsx +29 -0
- package/src/components/ai/Assistant.tsx +96 -0
- package/src/components/ai/Avatar.tsx +99 -0
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +73 -0
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +107 -0
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +96 -0
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +197 -0
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +129 -0
- package/src/components/ai/utils.ts +21 -0
- package/src/components/audio/Playbutton.tsx +126 -0
- package/src/hooks/I18nHooks.ts +33 -0
- package/src/hooks/UseChatHook.ts +38 -0
- package/src/plugin/ThemeSetter.ts +23 -0
- package/src/providers/PluginProvider.tsx +197 -0
- package/src/style.scss +136 -0
- package/src/utils/FullscreenUtils.ts +22 -0
- package/tsconfig.json +23 -0
package/README copy.md
ADDED
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
# Rimori Client Package
|
|
2
|
+
|
|
3
|
+
The **@rimori/client** package is a comprehensive React library that enables plugins to seamlessly integrate with the Rimori learning platform. It provides database access, AI/LLM integration, inter-plugin communication, community features, and pre-built UI components.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick Start Plugin Development](#quick-start-plugin-development)
|
|
9
|
+
- [Releasing Your Plugin to Rimori](#releasing-your-plugin-to-rimori)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Core API - usePlugin Hook](#core-api---useplugin-hook)
|
|
12
|
+
- [Database Integration](#database-integration)
|
|
13
|
+
- [LLM Integration](#llm-integration)
|
|
14
|
+
- [Event System](#event-system)
|
|
15
|
+
- [Community Features](#community-features)
|
|
16
|
+
- [Components](#components)
|
|
17
|
+
- [Hooks](#hooks)
|
|
18
|
+
- [Utilities](#utilities)
|
|
19
|
+
- [TypeScript Support](#typescript-support)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @rimori/client
|
|
26
|
+
# or
|
|
27
|
+
yarn add @rimori/client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start Plugin Development
|
|
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
|
+
|
|
43
|
+
Open Lovable and vibe code the look of your plugin.
|
|
44
|
+
|
|
45
|
+
Then connect it to your Github account.
|
|
46
|
+
|
|
47
|
+
Clone the git repository:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone ...
|
|
51
|
+
cd my-awesome-plugin
|
|
52
|
+
|
|
53
|
+
# Initialize with Rimori Client (this handles everything!)
|
|
54
|
+
npx @rimori/client rimori-init
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### What the Init Script Does
|
|
58
|
+
|
|
59
|
+
The `rimori-init` command automates the entire plugin setup process:
|
|
60
|
+
|
|
61
|
+
1. **🔐 Authentication**: Prompts for your Rimori credentials and authenticates with the platform
|
|
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
|
|
69
|
+
|
|
70
|
+
### Upgrade Mode
|
|
71
|
+
|
|
72
|
+
If you need to upgrade an existing plugin's configuration without changing the plugin ID:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
yarn rimori-init --upgrade
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Development Setup
|
|
79
|
+
|
|
80
|
+
After initialization, start developing immediately:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Start development server
|
|
84
|
+
yarn dev
|
|
85
|
+
|
|
86
|
+
# Your plugin will be available at:
|
|
87
|
+
# http://localhost:3000 (or your chosen port)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Rimori client comes pre-configured with:
|
|
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
|
|
97
|
+
|
|
98
|
+
## Releasing Your Plugin to Rimori
|
|
99
|
+
|
|
100
|
+
Publishing your plugin to the Rimori platform is streamlined through the built-in `rimori-release` CLI tool. The release process handles database updates, file uploads, and plugin activation automatically.
|
|
101
|
+
|
|
102
|
+
### Prerequisites
|
|
103
|
+
|
|
104
|
+
1. **Plugin must be initialized** - Use `rimori-init` first to set up your plugin
|
|
105
|
+
2. **Build your plugin** - Ensure your plugin is built and the output is in the `dist/` directory
|
|
106
|
+
3. **Environment configured** - Your `.env` file should contain `RIMORI_TOKEN` (set during initialization)
|
|
107
|
+
|
|
108
|
+
### Quick Release (Recommended)
|
|
109
|
+
|
|
110
|
+
During plugin initialization, convenient release scripts are automatically added to your `package.json`. These scripts handle building and releasing in one command:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Quick release commands (build + release)
|
|
114
|
+
yarn release:alpha # Build and release to alpha channel
|
|
115
|
+
yarn release:beta # Build and release to beta channel
|
|
116
|
+
yarn release:stable # Build and release to stable channel
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Manual Release Process
|
|
120
|
+
|
|
121
|
+
If you prefer more control or need to understand the underlying process, you can use the `rimori-release` command directly:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Build your plugin first
|
|
125
|
+
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
|
+
|
|
172
|
+
```
|
|
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
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// ✅ Correct - import from rimori client
|
|
189
|
+
import { RimoriClient } from '@rimori/client/core';
|
|
190
|
+
|
|
191
|
+
// ❌ Incorrect - direct imports that might access process.env directly or through their dependencies
|
|
192
|
+
import { Avatar } from '@rimori/client';
|
|
193
|
+
import { useQuery } from '@tanstack/react-query';
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
3. **Check your dependencies**: Look at your `package.json` for libraries that might be bundled into the worker:
|
|
197
|
+
- UI frameworks (React, Vue, etc.)
|
|
198
|
+
- State management libraries
|
|
199
|
+
- Routing libraries
|
|
200
|
+
- Any library that checks `process.env.NODE_ENV`
|
|
201
|
+
|
|
202
|
+
**Solution**: Use only `@rimori/client/core` exports in your worker code. The Rimori client provides all necessary functionality without requiring Node.js globals.
|
|
203
|
+
|
|
204
|
+
**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.
|
|
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
|
+
````
|
|
224
|
+
|
|
225
|
+
### Plugin Interface
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
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
|
+
### PlayButton
|
|
827
|
+
|
|
828
|
+
Audio playback component:
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
import { PlayButton } from "@rimori/client";
|
|
832
|
+
|
|
833
|
+
const AudioPlayer = () => {
|
|
834
|
+
return (
|
|
835
|
+
<PlayButton
|
|
836
|
+
audioUrl="https://example.com/audio.mp3"
|
|
837
|
+
onPlay={() => console.log('Audio playing')}
|
|
838
|
+
onPause={() => console.log('Audio paused')}
|
|
839
|
+
/>
|
|
840
|
+
);
|
|
841
|
+
};
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### AI Components
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
import { Avatar, Assistant } from "@rimori/client";
|
|
848
|
+
|
|
849
|
+
const AIInterface = () => {
|
|
850
|
+
return (
|
|
851
|
+
<div>
|
|
852
|
+
<Avatar
|
|
853
|
+
name="AI Assistant"
|
|
854
|
+
status="online"
|
|
855
|
+
size="large"
|
|
856
|
+
/>
|
|
857
|
+
|
|
858
|
+
<Assistant
|
|
859
|
+
onMessage={(message) => console.log('AI message:', message)}
|
|
860
|
+
placeholder="Ask the AI assistant..."
|
|
861
|
+
/>
|
|
862
|
+
</div>
|
|
863
|
+
);
|
|
864
|
+
};
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
## Hooks
|
|
868
|
+
|
|
869
|
+
### useChat
|
|
870
|
+
|
|
871
|
+
Manage AI chat conversations:
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
import { useChat } from "@rimori/client";
|
|
875
|
+
|
|
876
|
+
const ChatExample = () => {
|
|
877
|
+
const { messages, append, isLoading, setMessages } = useChat([
|
|
878
|
+
// Optional tools for the AI
|
|
879
|
+
{
|
|
880
|
+
name: "get_weather",
|
|
881
|
+
description: "Get current weather",
|
|
882
|
+
parameters: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {
|
|
885
|
+
location: { type: "string" }
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
]);
|
|
890
|
+
|
|
891
|
+
const sendMessage = (content: string) => {
|
|
892
|
+
append([{ role: 'user', content }]);
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
return (
|
|
896
|
+
<div>
|
|
897
|
+
{messages.map((msg, index) => (
|
|
898
|
+
<div key={index}>{msg.content}</div>
|
|
899
|
+
))}
|
|
900
|
+
{isLoading && <div>AI is typing...</div>}
|
|
901
|
+
</div>
|
|
902
|
+
);
|
|
903
|
+
};
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
### useTranslation
|
|
907
|
+
|
|
908
|
+
Internationalization (i18n) support built on i18next:
|
|
909
|
+
|
|
910
|
+
```typescript
|
|
911
|
+
import { useTranslation } from "@rimori/client";
|
|
912
|
+
|
|
913
|
+
const TranslatedComponent = () => {
|
|
914
|
+
const { t, ready } = useTranslation();
|
|
915
|
+
|
|
916
|
+
if (!ready) {
|
|
917
|
+
return <div>Loading translations...</div>;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return (
|
|
921
|
+
<div>
|
|
922
|
+
<h1>{t('discussion.title')}</h1>
|
|
923
|
+
<p>{t('discussion.whatToTalkAbout')}</p>
|
|
924
|
+
</div>
|
|
925
|
+
);
|
|
926
|
+
};
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
## Translation Feature
|
|
930
|
+
|
|
931
|
+
Rimori includes a comprehensive internationalization (i18n) system built on i18next that allows plugins to support multiple languages with minimal developer effort.
|
|
932
|
+
|
|
933
|
+
### How it works
|
|
934
|
+
|
|
935
|
+
- **Developer Focus**: Developers only need to ensure their interface works in English
|
|
936
|
+
- **Automatic Translations**: With every release, translations for all other languages are generated automatically
|
|
937
|
+
- **Local Testing**: For local development, you can test translations by:
|
|
938
|
+
1. Setting your user language to a non-English locale (e.g., German)
|
|
939
|
+
2. Creating a local translation file with "local-" prefix (e.g., `local-de.json`) in the `public/locales/` directory
|
|
940
|
+
3. The translator will automatically use the local translation file in development mode
|
|
941
|
+
- **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`)
|
|
942
|
+
|
|
943
|
+
### Usage
|
|
944
|
+
|
|
945
|
+
#### Using the Hook (Recommended)
|
|
946
|
+
|
|
947
|
+
```typescript
|
|
948
|
+
import { useTranslation } from "@rimori/client";
|
|
949
|
+
|
|
950
|
+
function MyComponent() {
|
|
951
|
+
const { t, ready } = useTranslation();
|
|
952
|
+
|
|
953
|
+
if (!ready) {
|
|
954
|
+
return <div>Loading translations...</div>;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return (
|
|
958
|
+
<div>
|
|
959
|
+
<h1>{t('discussion.title')}</h1>
|
|
960
|
+
<p>{t('discussion.whatToTalkAbout')}</p>
|
|
961
|
+
</div>
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
#### Using the Translator Instance
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
import { useRimori } from "@rimori/client";
|
|
970
|
+
|
|
971
|
+
const { plugin } = useRimori();
|
|
972
|
+
const translator = await plugin.getTranslator()
|
|
973
|
+
|
|
974
|
+
const translatedText = translator.t("discussion.title");
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### Translation File Structure
|
|
978
|
+
|
|
979
|
+
- **Location**: `public/locales/`
|
|
980
|
+
- **Production Files**: Must be named `{language}.json` (e.g., `en.json`, `de.json`, `fr.json`)
|
|
981
|
+
- **Local Development Files**: Must be named `local-{language}.json` (e.g., `local-de.json`, `local-fr.json`)
|
|
982
|
+
- **Format**: Standard JSON with nested objects for organization
|
|
983
|
+
- **English Requirement**: `en.json` is required as the base language
|
|
984
|
+
- **Release Process**: Files starting with "local-" are ignored during the release process
|
|
985
|
+
|
|
986
|
+
Example translation file structure:
|
|
987
|
+
|
|
988
|
+
```json
|
|
989
|
+
{
|
|
990
|
+
"discussion": {
|
|
991
|
+
"title": "Discussion",
|
|
992
|
+
"whatToTalkAbout": "What do you want to talk about?",
|
|
993
|
+
"topics": {
|
|
994
|
+
"everyday": {
|
|
995
|
+
"title": "Everyday Conversations",
|
|
996
|
+
"description": "Ordering coffee, asking for directions, etc."
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
### Features
|
|
1004
|
+
|
|
1005
|
+
- **I18next Support**: All i18next features work with these translations including:
|
|
1006
|
+
- Variable interpolation: `{{name}}`
|
|
1007
|
+
- Pluralization
|
|
1008
|
+
- Fallback mechanisms
|
|
1009
|
+
- **Automatic Fallback**: If a translation is missing, it falls back to English
|
|
1010
|
+
- **Development Mode**: Local translation files are prioritized in development
|
|
1011
|
+
- **Production Ready**: Automatic translation generation for production releases
|
|
1012
|
+
|
|
1013
|
+
### Limitations
|
|
1014
|
+
|
|
1015
|
+
- Only one translation file per language is allowed
|
|
1016
|
+
- Namespaces are not supported
|
|
1017
|
+
- Production translation files must be named `{language}.json` and placed in `public/locales/`
|
|
1018
|
+
- Local development files must be named `local-{language}.json` and placed in `public/locales/`
|
|
1019
|
+
- English (`en.json`) is required as the base language
|
|
1020
|
+
- Local files (prefixed with "local-") are ignored during the release process
|
|
1021
|
+
|
|
1022
|
+
## Utilities
|
|
1023
|
+
|
|
1024
|
+
### difficultyConverter
|
|
1025
|
+
|
|
1026
|
+
Convert between different difficulty representations:
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
import { difficultyConverter } from '@rimori/client';
|
|
1030
|
+
|
|
1031
|
+
const difficulty = difficultyConverter.toNumber('intermediate'); // Returns: 2
|
|
1032
|
+
const difficultyText = difficultyConverter.toString(3); // Returns: 'advanced'
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
### PluginUtils
|
|
1036
|
+
|
|
1037
|
+
Various utility functions:
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
import { PluginUtils } from '@rimori/client';
|
|
1041
|
+
|
|
1042
|
+
// Utility functions for common plugin operations
|
|
1043
|
+
const utils = PluginUtils.getInstance();
|
|
1044
|
+
// Access various helper methods
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Language Utilities
|
|
1048
|
+
|
|
1049
|
+
Language detection and processing:
|
|
1050
|
+
|
|
1051
|
+
```typescript
|
|
1052
|
+
import { Language } from '@rimori/client';
|
|
1053
|
+
|
|
1054
|
+
// Language-related utility functions
|
|
1055
|
+
const languageCode = Language.detectLanguage(text);
|
|
1056
|
+
const isSupported = Language.isSupported('es');
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
## TypeScript Support
|
|
1060
|
+
|
|
1061
|
+
The package is fully typed with comprehensive TypeScript definitions:
|
|
1062
|
+
|
|
1063
|
+
```typescript
|
|
1064
|
+
import type {
|
|
1065
|
+
MainPanelAction,
|
|
1066
|
+
Message,
|
|
1067
|
+
Tool,
|
|
1068
|
+
EventPayload,
|
|
1069
|
+
AccomplishmentPayload,
|
|
1070
|
+
SharedContent,
|
|
1071
|
+
BasicAssignment,
|
|
1072
|
+
UserInfo,
|
|
1073
|
+
} from '@rimori/client';
|
|
1074
|
+
|
|
1075
|
+
// All interfaces and types are exported for use in your plugin
|
|
1076
|
+
interface MyPluginData extends SharedContent<any> {
|
|
1077
|
+
// Your custom properties
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
The SharedContent has this type definition:
|
|
1082
|
+
|
|
1083
|
+
```
|
|
1084
|
+
export interface SharedContent<T> {
|
|
1085
|
+
/** The type/category of the content (e.g. 'grammar_exercises', 'flashcards', etc.) */
|
|
1086
|
+
contentType: string;
|
|
1087
|
+
|
|
1088
|
+
/** The human readable title/topic of the content */
|
|
1089
|
+
topic: string;
|
|
1090
|
+
|
|
1091
|
+
/** Array of keywords/tags associated with the content for search and categorization */
|
|
1092
|
+
keywords: string[];
|
|
1093
|
+
|
|
1094
|
+
/** The actual content data of type T */
|
|
1095
|
+
data: T;
|
|
1096
|
+
|
|
1097
|
+
/** Whether this content should only be visible to the creator. Defaults to false if not specified */
|
|
1098
|
+
privateTopic?: boolean;
|
|
1099
|
+
}
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
## Examples
|
|
1103
|
+
|
|
1104
|
+
### Complete Plugin Example
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
import React, { useState, useEffect } from 'react';
|
|
1108
|
+
import {
|
|
1109
|
+
PluginProvider,
|
|
1110
|
+
usePlugin,
|
|
1111
|
+
MarkdownEditor,
|
|
1112
|
+
Spinner,
|
|
1113
|
+
useChat
|
|
1114
|
+
} from '@rimori/client';
|
|
1115
|
+
import { HashRouter, Route, Routes } from 'react-router-dom';
|
|
1116
|
+
|
|
1117
|
+
const StudyNotesPlugin = () => {
|
|
1118
|
+
const { db, llm, plugin, community } = useRimori();
|
|
1119
|
+
const [notes, setNotes] = useState([]);
|
|
1120
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1121
|
+
const { messages, append } = useChat();
|
|
1122
|
+
|
|
1123
|
+
useEffect(() => {
|
|
1124
|
+
loadNotes();
|
|
1125
|
+
}, []);
|
|
1126
|
+
|
|
1127
|
+
const loadNotes = async () => {
|
|
1128
|
+
try {
|
|
1129
|
+
const { data } = await db.from('notes').select('*').order('created_at', { ascending: false });
|
|
1130
|
+
setNotes(data || []);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.error('Error loading notes:', error);
|
|
1133
|
+
} finally {
|
|
1134
|
+
setIsLoading(false);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
const saveNote = async (content: string) => {
|
|
1139
|
+
const { data } = await db.from('notes').insert({
|
|
1140
|
+
content,
|
|
1141
|
+
created_at: new Date().toISOString()
|
|
1142
|
+
}).select().single();
|
|
1143
|
+
|
|
1144
|
+
setNotes([data, ...notes]);
|
|
1145
|
+
|
|
1146
|
+
// Share with community
|
|
1147
|
+
await community.sharedContent.create({
|
|
1148
|
+
content_type: 'study_notes',
|
|
1149
|
+
content: { text: content },
|
|
1150
|
+
metadata: { wordCount: content.length }
|
|
1151
|
+
});
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
const generateSummary = async (noteContent: string) => {
|
|
1155
|
+
const summary = await llm.getText([
|
|
1156
|
+
{ role: 'user', content: `Summarize this study note: ${noteContent}` }
|
|
1157
|
+
]);
|
|
1158
|
+
|
|
1159
|
+
return summary;
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
if (isLoading) return <Spinner size="large" />;
|
|
1163
|
+
|
|
1164
|
+
return (
|
|
1165
|
+
<div className="study-notes-plugin">
|
|
1166
|
+
<h1>Study Notes</h1>
|
|
1167
|
+
|
|
1168
|
+
<div className="notes-grid">
|
|
1169
|
+
{notes.map(note => (
|
|
1170
|
+
<div key={note.id} className="note-card">
|
|
1171
|
+
<MarkdownEditor
|
|
1172
|
+
value={note.content}
|
|
1173
|
+
onChange={(content) => {/* Update logic */}}
|
|
1174
|
+
/>
|
|
1175
|
+
<button onClick={() => generateSummary(note.content)}>
|
|
1176
|
+
Generate Summary
|
|
1177
|
+
</button>
|
|
1178
|
+
</div>
|
|
1179
|
+
))}
|
|
1180
|
+
</div>
|
|
1181
|
+
|
|
1182
|
+
<div className="ai-chat">
|
|
1183
|
+
<h2>Study Assistant</h2>
|
|
1184
|
+
{messages.map((msg, index) => (
|
|
1185
|
+
<div key={index} className={`message ${msg.role}`}>
|
|
1186
|
+
{msg.content}
|
|
1187
|
+
</div>
|
|
1188
|
+
))}
|
|
1189
|
+
<button onClick={() => append([{ role: 'user', content: 'Help me study' }])}>
|
|
1190
|
+
Get Study Help
|
|
1191
|
+
</button>
|
|
1192
|
+
</div>
|
|
1193
|
+
</div>
|
|
1194
|
+
);
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
const App = () => (
|
|
1198
|
+
<PluginProvider pluginId="study-notes-plugin">
|
|
1199
|
+
<HashRouter>
|
|
1200
|
+
<Routes>
|
|
1201
|
+
<Route path="/" element={<StudyNotesPlugin />} />
|
|
1202
|
+
</Routes>
|
|
1203
|
+
</HashRouter>
|
|
1204
|
+
</PluginProvider>
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
export default App;
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
## Best Practices
|
|
1211
|
+
|
|
1212
|
+
1. **Performance**: Use lazy loading for pages and implement proper loading states
|
|
1213
|
+
2. **State Management**: Leverage React hooks and context when needed
|
|
1214
|
+
3. **Type Safety**: Use TypeScript interfaces for all data structures
|
|
1215
|
+
4. **Event Cleanup**: Always unsubscribe from events in useEffect cleanup
|
|
1216
|
+
5. **Responsive Design**: Use TailwindCSS classes for responsive layouts
|