@rimori/react-client 0.4.6 → 0.4.7
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 +12 -9
- package/dist/hooks/I18nHooks.js +8 -5
- package/dist/hooks/ThemeSetter.js +2 -0
- package/package.json +3 -3
- package/src/hooks/I18nHooks.ts +18 -12
- package/src/hooks/ThemeSetter.ts +2 -0
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
The `@rimori/react-client` package contains the React bindings for the Rimori plugin runtime. Wrap your plugin UI with the provided context, use the hooks to talk to the Rimori platform, and drop in prebuilt assistant components without writing your own wiring.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
|
+
|
|
6
7
|
- [Overview](#overview)
|
|
7
8
|
- [Installation](#installation)
|
|
8
9
|
- [When to Use](#when-to-use)
|
|
@@ -17,6 +18,7 @@ The `@rimori/react-client` package contains the React bindings for the Rimori pl
|
|
|
17
18
|
## Overview
|
|
18
19
|
|
|
19
20
|
`@rimori/react-client` builds on top of `@rimori/client` and provides:
|
|
21
|
+
|
|
20
22
|
- `PluginProvider` that initializes the Rimori runtime, sets up the event bus, and injects the Rimori context into React.
|
|
21
23
|
- React hooks for AI chat, translation, and direct access to the Rimori client instance.
|
|
22
24
|
- Prebuilt components for voice-enabled assistants, avatars, audio playback, and the Rimori context menu.
|
|
@@ -35,6 +37,7 @@ Both React 18 and `@rimori/client` are peer dependencies. Keep them in sync with
|
|
|
35
37
|
## When to Use
|
|
36
38
|
|
|
37
39
|
Choose this package whenever your plugin UI is written in React and you need:
|
|
40
|
+
|
|
38
41
|
- Access to the `RimoriClient` through idiomatic hooks (`useRimori`).
|
|
39
42
|
- Streamed AI chat experiences without managing the event bus yourself.
|
|
40
43
|
- Drop-in UI for the Rimori assistant, avatars, contextual menus, and audio controls.
|
|
@@ -45,8 +48,8 @@ If you need to interact with the Rimori platform outside of React (workers, CLI,
|
|
|
45
48
|
## Quick Start
|
|
46
49
|
|
|
47
50
|
```tsx
|
|
48
|
-
import
|
|
49
|
-
import { PluginProvider, useRimori, useChat, useTranslation } from
|
|
51
|
+
import '@rimori/react-client/dist/style.css';
|
|
52
|
+
import { PluginProvider, useRimori, useChat, useTranslation } from '@rimori/react-client';
|
|
50
53
|
|
|
51
54
|
function Dashboard() {
|
|
52
55
|
const client = useRimori();
|
|
@@ -54,14 +57,14 @@ function Dashboard() {
|
|
|
54
57
|
const { messages, append, isLoading } = useChat();
|
|
55
58
|
|
|
56
59
|
const send = () => {
|
|
57
|
-
append([{ role:
|
|
60
|
+
append([{ role: 'user', content: t('discussion.prompts.askForHelp') }]);
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
return (
|
|
61
64
|
<div>
|
|
62
|
-
<h1>{t(
|
|
65
|
+
<h1>{t('discussion.title')}</h1>
|
|
63
66
|
<button onClick={send} disabled={isLoading}>
|
|
64
|
-
{t(
|
|
67
|
+
{t('common.buttons.getStarted')}
|
|
65
68
|
</button>
|
|
66
69
|
<pre>{JSON.stringify(messages, null, 2)}</pre>
|
|
67
70
|
<p>{client.plugin.pluginId}</p>
|
|
@@ -82,7 +85,7 @@ export function App() {
|
|
|
82
85
|
|
|
83
86
|
- `useRimori()` – returns the underlying `RimoriClient` instance (database, AI, community, event bus, translator access, etc.). Throws if used outside `PluginProvider`.
|
|
84
87
|
- `useChat(tools?)` – manages streaming conversations with the Rimori AI. Provides `messages`, `append`, `isLoading`, `setMessages`, and `lastMessage`.
|
|
85
|
-
- `useTranslation()` – returns `{ t, ready }` where `t` is a safe translator bound to the current user language. Use it for every user-visible string, following the Rimori translation key conventions.
|
|
88
|
+
- `useTranslation()` – returns `{ t, ready }` where `t` is a safe translator bound to the current user language. Use it for every user-visible string, following the Rimori translation key conventions. If you pass a non-key string (does not match `segment.segment` with at least one dot), the runtime will translate it via AI and cache the result.
|
|
86
89
|
|
|
87
90
|
## Components
|
|
88
91
|
|
|
@@ -99,7 +102,7 @@ All components require being rendered inside `PluginProvider` so they can reach
|
|
|
99
102
|
Import the generated stylesheet once in your app entry point:
|
|
100
103
|
|
|
101
104
|
```ts
|
|
102
|
-
import
|
|
105
|
+
import '@rimori/react-client/dist/style.css';
|
|
103
106
|
```
|
|
104
107
|
|
|
105
108
|
The stylesheet contains base styles for the assistant components and ensures they respond to the Rimori theme tokens that are passed through the sandbox handshake.
|
|
@@ -115,7 +118,7 @@ The stylesheet contains base styles for the assistant components and ensures the
|
|
|
115
118
|
## Example
|
|
116
119
|
|
|
117
120
|
```tsx
|
|
118
|
-
import { PluginProvider, Assistant, useRimori } from
|
|
121
|
+
import { PluginProvider, Assistant, useRimori } from '@rimori/react-client';
|
|
119
122
|
|
|
120
123
|
function AssistantPanel() {
|
|
121
124
|
const client = useRimori();
|
|
@@ -124,7 +127,7 @@ function AssistantPanel() {
|
|
|
124
127
|
<div className="assistant-panel">
|
|
125
128
|
<Assistant
|
|
126
129
|
placeholderKey="discussion.input.placeholder"
|
|
127
|
-
onMessage={(message) => client.event.emit(
|
|
130
|
+
onMessage={(message) => client.event.emit('self.discussion.onAssistantMessage', { message })}
|
|
128
131
|
/>
|
|
129
132
|
</div>
|
|
130
133
|
);
|
package/dist/hooks/I18nHooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useRimori } from '../providers/PluginProvider';
|
|
3
3
|
/**
|
|
4
4
|
* Custom useTranslation hook that provides a translation function and indicates readiness
|
|
@@ -7,19 +7,22 @@ import { useRimori } from '../providers/PluginProvider';
|
|
|
7
7
|
export function useTranslation() {
|
|
8
8
|
const { plugin } = useRimori();
|
|
9
9
|
const [translatorInstance, setTranslatorInstance] = useState(null);
|
|
10
|
+
const [updateCount, setUpdateCount] = useState(0);
|
|
10
11
|
useEffect(() => {
|
|
11
|
-
void plugin.getTranslator().then(
|
|
12
|
+
void plugin.getTranslator().then((translator) => {
|
|
13
|
+
setTranslatorInstance(translator);
|
|
14
|
+
translator.onLanguageChanged(() => setUpdateCount(updateCount + 1));
|
|
15
|
+
});
|
|
12
16
|
}, [plugin]);
|
|
13
|
-
const safeT = (key, options) => {
|
|
17
|
+
const safeT = useCallback((key, options) => {
|
|
14
18
|
// return zero-width space if translator is not initialized to keep text space occupied
|
|
15
19
|
if (!translatorInstance)
|
|
16
20
|
return '\u200B'; // zero-width space
|
|
17
21
|
const result = translatorInstance.t(key, options);
|
|
18
22
|
if (!result) {
|
|
19
|
-
console.error(`Translation key not found: ${key}`);
|
|
20
23
|
return '\u200B'; // zero-width space
|
|
21
24
|
}
|
|
22
25
|
return result;
|
|
23
|
-
};
|
|
26
|
+
}, [translatorInstance, updateCount]);
|
|
24
27
|
return { t: safeT, ready: translatorInstance !== null };
|
|
25
28
|
}
|
|
@@ -3,6 +3,8 @@ export function useTheme(theme = 'system') {
|
|
|
3
3
|
const dom = document.documentElement;
|
|
4
4
|
dom.style.background = 'hsl(var(--background))';
|
|
5
5
|
dom.classList.add('text-gray-900', 'dark:text-gray-200', 'bg-gray-50', 'dark:bg-gray-950');
|
|
6
|
+
dom.dataset.theme = isDark ? 'dark' : 'light';
|
|
7
|
+
dom.style.colorScheme = isDark ? 'dark' : 'light';
|
|
6
8
|
const root = document.querySelector('#root');
|
|
7
9
|
root.style.background = 'hsl(var(--background))';
|
|
8
10
|
dom.classList[isDark ? 'add' : 'remove']('dark');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/react-client",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"format": "prettier --write ."
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@rimori/client": "^2.5.
|
|
26
|
+
"@rimori/client": "^2.5.10",
|
|
27
27
|
"react": "^18.1.0",
|
|
28
28
|
"react-dom": "^18.1.0"
|
|
29
29
|
},
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@eslint/js": "^9.37.0",
|
|
37
|
-
"@rimori/client": "^2.5.
|
|
37
|
+
"@rimori/client": "^2.5.10",
|
|
38
38
|
"@types/react": "^18.3.21",
|
|
39
39
|
"eslint-config-prettier": "^10.1.8",
|
|
40
40
|
"eslint-plugin-prettier": "^5.5.4",
|
package/src/hooks/I18nHooks.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TOptions } from '@rimori/client';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { Translator } from '@rimori/client';
|
|
4
4
|
import { useRimori } from '../providers/PluginProvider';
|
|
5
5
|
|
|
@@ -12,22 +12,28 @@ type TranslatorFn = (key: string, options?: TOptions) => string;
|
|
|
12
12
|
export function useTranslation(): { t: TranslatorFn; ready: boolean } {
|
|
13
13
|
const { plugin } = useRimori();
|
|
14
14
|
const [translatorInstance, setTranslatorInstance] = useState<Translator | null>(null);
|
|
15
|
+
const [updateCount, setUpdateCount] = useState(0);
|
|
15
16
|
|
|
16
17
|
useEffect(() => {
|
|
17
|
-
void plugin.getTranslator().then(
|
|
18
|
+
void plugin.getTranslator().then((translator) => {
|
|
19
|
+
setTranslatorInstance(translator);
|
|
20
|
+
translator.onLanguageChanged(() => setUpdateCount(updateCount + 1));
|
|
21
|
+
});
|
|
18
22
|
}, [plugin]);
|
|
19
23
|
|
|
20
|
-
const safeT = (
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const safeT = useCallback(
|
|
25
|
+
(key: string, options?: TOptions): string => {
|
|
26
|
+
// return zero-width space if translator is not initialized to keep text space occupied
|
|
27
|
+
if (!translatorInstance) return '\u200B'; // zero-width space
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const result = translatorInstance.t(key, options);
|
|
30
|
+
if (!result) {
|
|
31
|
+
return '\u200B'; // zero-width space
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
},
|
|
35
|
+
[translatorInstance, updateCount],
|
|
36
|
+
);
|
|
31
37
|
|
|
32
38
|
return { t: safeT, ready: translatorInstance !== null };
|
|
33
39
|
}
|
package/src/hooks/ThemeSetter.ts
CHANGED
|
@@ -6,6 +6,8 @@ export function useTheme(theme: Theme = 'system'): { isDark: boolean; theme: The
|
|
|
6
6
|
const dom = document.documentElement;
|
|
7
7
|
dom.style.background = 'hsl(var(--background))';
|
|
8
8
|
dom.classList.add('text-gray-900', 'dark:text-gray-200', 'bg-gray-50', 'dark:bg-gray-950');
|
|
9
|
+
dom.dataset.theme = isDark ? 'dark' : 'light';
|
|
10
|
+
dom.style.colorScheme = isDark ? 'dark' : 'light';
|
|
9
11
|
|
|
10
12
|
const root = document.querySelector('#root') as HTMLDivElement;
|
|
11
13
|
root.style.background = 'hsl(var(--background))';
|