@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 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 "@rimori/react-client/dist/style.css";
49
- import { PluginProvider, useRimori, useChat, useTranslation } from "@rimori/react-client";
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: "user", content: t("discussion.prompts.askForHelp") }]);
60
+ append([{ role: 'user', content: t('discussion.prompts.askForHelp') }]);
58
61
  };
59
62
 
60
63
  return (
61
64
  <div>
62
- <h1>{t("discussion.title")}</h1>
65
+ <h1>{t('discussion.title')}</h1>
63
66
  <button onClick={send} disabled={isLoading}>
64
- {t("common.buttons.getStarted")}
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 "@rimori/react-client/dist/style.css";
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 "@rimori/react-client";
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("self.discussion.onAssistantMessage", { message })}
130
+ onMessage={(message) => client.event.emit('self.discussion.onAssistantMessage', { message })}
128
131
  />
129
132
  </div>
130
133
  );
@@ -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(setTranslatorInstance);
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.6",
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.8",
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.8",
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",
@@ -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(setTranslatorInstance);
18
+ void plugin.getTranslator().then((translator) => {
19
+ setTranslatorInstance(translator);
20
+ translator.onLanguageChanged(() => setUpdateCount(updateCount + 1));
21
+ });
18
22
  }, [plugin]);
19
23
 
20
- const safeT = (key: string, options?: TOptions): string => {
21
- // return zero-width space if translator is not initialized to keep text space occupied
22
- if (!translatorInstance) return '\u200B'; // zero-width space
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
- const result = translatorInstance.t(key, options);
25
- if (!result) {
26
- console.error(`Translation key not found: ${key}`);
27
- return '\u200B'; // zero-width space
28
- }
29
- return result;
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
  }
@@ -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))';