@rimori/react-client 0.1.0 → 0.2.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 +142 -1
- package/dist/{react-client/src/components → components}/ContextMenu.js +1 -2
- package/dist/components/Spinner.d.ts +0 -7
- package/dist/components/Spinner.js +1 -4
- package/dist/components/ai/Assistant.js +1 -1
- package/dist/components/ai/Avatar.d.ts +2 -3
- package/dist/components/ai/Avatar.js +6 -4
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +1 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -2
- package/dist/components/audio/Playbutton.js +13 -9
- package/dist/hooks/I18nHooks.d.ts +1 -1
- package/dist/{react-client/src/plugin → hooks}/ThemeSetter.d.ts +1 -1
- package/dist/hooks/ThemeSetter.js +31 -0
- package/dist/hooks/UseChatHook.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/{react-client/plugin → plugin}/ThemeSetter.d.ts +1 -1
- package/dist/plugin/ThemeSetter.js +31 -0
- package/dist/providers/PluginProvider.d.ts +2 -1
- package/dist/providers/PluginProvider.js +10 -7
- package/dist/{react-client/src/utils → utils}/FullscreenUtils.js +2 -2
- package/package.json +4 -7
- package/src/components/ContextMenu.tsx +2 -2
- package/src/components/ai/Avatar.tsx +9 -4
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +1 -1
- package/src/components/audio/Playbutton.tsx +28 -1
- package/src/hooks/ThemeSetter.ts +40 -0
- package/src/index.ts +10 -0
- package/src/providers/PluginProvider.tsx +12 -8
- package/tsconfig.json +6 -12
- package/dist/components/components/ContextMenu.d.ts +0 -10
- package/dist/components/components/ContextMenu.js +0 -135
- package/dist/react-client/plugin/ThemeSetter.js +0 -19
- package/dist/react-client/src/components/MarkdownEditor.d.ts +0 -8
- package/dist/react-client/src/components/MarkdownEditor.js +0 -48
- package/dist/react-client/src/components/Spinner.d.ts +0 -8
- package/dist/react-client/src/components/Spinner.js +0 -4
- package/dist/react-client/src/components/ai/Assistant.d.ts +0 -9
- package/dist/react-client/src/components/ai/Assistant.js +0 -58
- package/dist/react-client/src/components/ai/Avatar.d.ts +0 -14
- package/dist/react-client/src/components/ai/Avatar.js +0 -59
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.d.ts +0 -7
- package/dist/react-client/src/components/ai/EmbeddedAssistent/AudioInputField.js +0 -37
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -8
- package/dist/react-client/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +0 -79
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/MessageSender.js +0 -91
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.d.ts +0 -27
- package/dist/react-client/src/components/ai/EmbeddedAssistent/TTS/Player.js +0 -185
- package/dist/react-client/src/components/ai/utils.d.ts +0 -6
- package/dist/react-client/src/components/ai/utils.js +0 -13
- package/dist/react-client/src/components/audio/Playbutton.d.ts +0 -15
- package/dist/react-client/src/components/audio/Playbutton.js +0 -82
- package/dist/react-client/src/components/components/ContextMenu.d.ts +0 -10
- package/dist/react-client/src/components/components/ContextMenu.js +0 -135
- package/dist/react-client/src/hooks/I18nHooks.d.ts +0 -11
- package/dist/react-client/src/hooks/I18nHooks.js +0 -25
- package/dist/react-client/src/hooks/UseChatHook.d.ts +0 -10
- package/dist/react-client/src/hooks/UseChatHook.js +0 -29
- package/dist/react-client/src/plugin/ThemeSetter.js +0 -19
- package/dist/react-client/src/providers/PluginProvider.d.ts +0 -12
- package/dist/react-client/src/providers/PluginProvider.js +0 -142
- package/dist/react-client/src/utils/PluginUtils.d.ts +0 -2
- package/dist/react-client/src/utils/PluginUtils.js +0 -23
- package/dist/rimori-client/src/cli/types/DatabaseTypes.d.ts +0 -103
- package/dist/rimori-client/src/cli/types/DatabaseTypes.js +0 -2
- package/dist/rimori-client/src/controller/AIController.d.ts +0 -15
- package/dist/rimori-client/src/controller/AIController.js +0 -255
- package/dist/rimori-client/src/controller/AccomplishmentController.d.ts +0 -38
- package/dist/rimori-client/src/controller/AccomplishmentController.js +0 -112
- package/dist/rimori-client/src/controller/AudioController.d.ts +0 -37
- package/dist/rimori-client/src/controller/AudioController.js +0 -68
- package/dist/rimori-client/src/controller/ExerciseController.d.ts +0 -54
- package/dist/rimori-client/src/controller/ExerciseController.js +0 -74
- package/dist/rimori-client/src/controller/ObjectController.d.ts +0 -42
- package/dist/rimori-client/src/controller/ObjectController.js +0 -76
- package/dist/rimori-client/src/controller/SettingsController.d.ts +0 -79
- package/dist/rimori-client/src/controller/SettingsController.js +0 -118
- package/dist/rimori-client/src/controller/SharedContentController.d.ts +0 -106
- package/dist/rimori-client/src/controller/SharedContentController.js +0 -285
- package/dist/rimori-client/src/controller/TranslationController.d.ts +0 -38
- package/dist/rimori-client/src/controller/TranslationController.js +0 -106
- package/dist/rimori-client/src/controller/VoiceController.d.ts +0 -9
- package/dist/rimori-client/src/controller/VoiceController.js +0 -37
- package/dist/rimori-client/src/fromRimori/EventBus.d.ts +0 -101
- package/dist/rimori-client/src/fromRimori/EventBus.js +0 -263
- package/dist/rimori-client/src/fromRimori/PluginTypes.d.ts +0 -174
- package/dist/rimori-client/src/fromRimori/PluginTypes.js +0 -1
- package/dist/rimori-client/src/index.d.ts +0 -11
- package/dist/rimori-client/src/index.js +0 -10
- package/dist/rimori-client/src/plugin/CommunicationHandler.d.ts +0 -48
- package/dist/rimori-client/src/plugin/CommunicationHandler.js +0 -234
- package/dist/rimori-client/src/plugin/Logger.d.ts +0 -73
- package/dist/rimori-client/src/plugin/Logger.js +0 -308
- package/dist/rimori-client/src/plugin/RimoriClient.d.ts +0 -258
- package/dist/rimori-client/src/plugin/RimoriClient.js +0 -375
- package/dist/rimori-client/src/plugin/StandaloneClient.d.ts +0 -17
- package/dist/rimori-client/src/plugin/StandaloneClient.js +0 -115
- package/dist/rimori-client/src/utils/difficultyConverter.d.ts +0 -4
- package/dist/rimori-client/src/utils/difficultyConverter.js +0 -10
- package/dist/rimori-client/src/utils/endpoint.d.ts +0 -2
- package/dist/rimori-client/src/utils/endpoint.js +0 -2
- package/dist/utils/PluginUtils.d.ts +0 -2
- package/dist/utils/PluginUtils.js +0 -23
- package/index.ts +0 -6
- package/src/components/MarkdownEditor.tsx +0 -144
- package/src/components/Spinner.tsx +0 -29
- package/src/plugin/ThemeSetter.ts +0 -23
- package/src/utils/FullscreenUtils.ts +0 -22
- /package/dist/{react-client/src/components → components}/ContextMenu.d.ts +0 -0
- /package/dist/{react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts → components/ai/EmbeddedAssistent/VoiceRecorder.d.ts} +0 -0
- /package/dist/{react-client/src/components/ai/EmbeddedAssistent/VoiceRecoder.js → components/ai/EmbeddedAssistent/VoiceRecorder.js} +0 -0
- /package/dist/{react-client/src/utils → utils}/FullscreenUtils.d.ts +0 -0
- /package/src/components/ai/EmbeddedAssistent/{VoiceRecoder.tsx → VoiceRecorder.tsx} +0 -0
package/README.md
CHANGED
|
@@ -1 +1,142 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Rimori React Client
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [When to Use](#when-to-use)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Hooks](#hooks)
|
|
11
|
+
- [Components](#components)
|
|
12
|
+
- [Styling](#styling)
|
|
13
|
+
- [Standalone Development](#standalone-development)
|
|
14
|
+
- [Additional Exports](#additional-exports)
|
|
15
|
+
- [Example](#example)
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
`@rimori/react-client` builds on top of `@rimori/client` and provides:
|
|
20
|
+
- `PluginProvider` that initializes the Rimori runtime, sets up the event bus, and injects the Rimori context into React.
|
|
21
|
+
- React hooks for AI chat, translation, and direct access to the Rimori client instance.
|
|
22
|
+
- Prebuilt components for voice-enabled assistants, avatars, audio playback, and the Rimori context menu.
|
|
23
|
+
- Automatic handling of theme propagation, standalone authentication, and URL tracking inside the Rimori shell.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @rimori/react-client @rimori/client react react-dom
|
|
29
|
+
# or
|
|
30
|
+
yarn add @rimori/react-client @rimori/client react react-dom
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Both React 18 and `@rimori/client` are peer dependencies. Keep them in sync with the versions used by the Rimori platform.
|
|
34
|
+
|
|
35
|
+
## When to Use
|
|
36
|
+
|
|
37
|
+
Choose this package whenever your plugin UI is written in React and you need:
|
|
38
|
+
- Access to the `RimoriClient` through idiomatic hooks (`useRimori`).
|
|
39
|
+
- Streamed AI chat experiences without managing the event bus yourself.
|
|
40
|
+
- Drop-in UI for the Rimori assistant, avatars, contextual menus, and audio controls.
|
|
41
|
+
- A translation helper that respects the Rimori i18next pipeline.
|
|
42
|
+
|
|
43
|
+
If you need to interact with the Rimori platform outside of React (workers, CLI, background scripts), keep using `@rimori/client` directly.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import "@rimori/react-client/dist/style.css";
|
|
49
|
+
import { PluginProvider, useRimori, useChat, useTranslation } from "@rimori/react-client";
|
|
50
|
+
|
|
51
|
+
function Dashboard() {
|
|
52
|
+
const client = useRimori();
|
|
53
|
+
const { t } = useTranslation();
|
|
54
|
+
const { messages, append, isLoading } = useChat();
|
|
55
|
+
|
|
56
|
+
const send = () => {
|
|
57
|
+
append([{ role: "user", content: t("discussion.prompts.askForHelp") }]);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<h1>{t("discussion.title")}</h1>
|
|
63
|
+
<button onClick={send} disabled={isLoading}>
|
|
64
|
+
{t("common.buttons.getStarted")}
|
|
65
|
+
</button>
|
|
66
|
+
<pre>{JSON.stringify(messages, null, 2)}</pre>
|
|
67
|
+
<p>{client.plugin.pluginId}</p>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function App() {
|
|
73
|
+
return (
|
|
74
|
+
<PluginProvider pluginId="your-plugin-id">
|
|
75
|
+
<Dashboard />
|
|
76
|
+
</PluginProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Hooks
|
|
82
|
+
|
|
83
|
+
- `useRimori()` – returns the underlying `RimoriClient` instance (database, AI, community, event bus, translator access, etc.). Throws if used outside `PluginProvider`.
|
|
84
|
+
- `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.
|
|
86
|
+
|
|
87
|
+
## Components
|
|
88
|
+
|
|
89
|
+
All components require being rendered inside `PluginProvider` so they can reach the shared client instance.
|
|
90
|
+
|
|
91
|
+
- `Assistant` – full chat UI shell with streaming responses and optional tool invocation support.
|
|
92
|
+
- `Avatar` – status-aware avatar component that reflects assistant state.
|
|
93
|
+
- `EmbeddedAssistent` utilities – granular building blocks (`CircleAudioAvatar`, `VoiceRecorder`, TTS `Player`, etc.) for composing your own assistant flows.
|
|
94
|
+
- `PlayButton` – accessible audio playback button that integrates with Rimori's audio controller.
|
|
95
|
+
- `ContextMenu` – automatically injected by `PluginProvider` (can be disabled via `settings.disableContextMenu`).
|
|
96
|
+
|
|
97
|
+
## Styling
|
|
98
|
+
|
|
99
|
+
Import the generated stylesheet once in your app entry point:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import "@rimori/react-client/dist/style.css";
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
## Standalone Development
|
|
108
|
+
|
|
109
|
+
`PluginProvider` detects when the plugin is not hosted inside Rimori and automatically spins up `StandaloneClient`. During local development it shows a login overlay that lets you authenticate with your Rimori developer account before establishing the runtime connection.
|
|
110
|
+
|
|
111
|
+
## Additional Exports
|
|
112
|
+
|
|
113
|
+
- `FirstMessages` – helper preset with common greeting messages for assistant onboarding flows.
|
|
114
|
+
|
|
115
|
+
## Example
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { PluginProvider, Assistant, useRimori } from "@rimori/react-client";
|
|
119
|
+
|
|
120
|
+
function AssistantPanel() {
|
|
121
|
+
const client = useRimori();
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="assistant-panel">
|
|
125
|
+
<Assistant
|
|
126
|
+
placeholderKey="discussion.input.placeholder"
|
|
127
|
+
onMessage={(message) => client.event.emit("self.discussion.onAssistantMessage", { message })}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function Root() {
|
|
134
|
+
return (
|
|
135
|
+
<PluginProvider pluginId="your-plugin-id">
|
|
136
|
+
<AssistantPanel />
|
|
137
|
+
</PluginProvider>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pair this React package with the updated `@rimori/client` README to understand every controller that is available on the returned `RimoriClient` instance.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { EventBus } from '@rimori/client';
|
|
4
3
|
const ContextMenu = ({ client }) => {
|
|
5
4
|
const [isOpen, setIsOpen] = useState(false);
|
|
6
5
|
const [actions, setActions] = useState([]);
|
|
@@ -36,7 +35,7 @@ const ContextMenu = ({ client }) => {
|
|
|
36
35
|
.filter(Boolean);
|
|
37
36
|
setActions(actions);
|
|
38
37
|
setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
|
|
39
|
-
|
|
38
|
+
client.event.on('global.contextMenu.createActions', ({ data }) => {
|
|
40
39
|
setActions([...data.actions, ...actions]);
|
|
41
40
|
});
|
|
42
41
|
}, []);
|
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export const Spinner = ({ text, className, size = '30px' }) => {
|
|
3
|
-
return (_jsxs("div", { className: 'flex items-center space-x-2 ' + className, children: [_jsxs("svg", { style: { width: size, height: size }, className: "animate-spin -ml-1 mr-3 h-5 w-5 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), text && _jsx("span", { className: "", children: text })] }));
|
|
4
|
-
};
|
|
1
|
+
export {};
|
|
@@ -5,7 +5,7 @@ import { AudioInputField } from './EmbeddedAssistent/AudioInputField';
|
|
|
5
5
|
import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
|
|
6
6
|
import Markdown from 'react-markdown';
|
|
7
7
|
import { useChat } from '../../hooks/UseChatHook';
|
|
8
|
-
import { useRimori } from '../../
|
|
8
|
+
import { useRimori } from '../../providers/PluginProvider';
|
|
9
9
|
import { getFirstMessages } from './utils';
|
|
10
10
|
export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }) {
|
|
11
11
|
var _a;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { Tool } from '
|
|
1
|
+
import { Tool } from '@rimori/client';
|
|
2
2
|
import { FirstMessages } from './utils';
|
|
3
3
|
interface Props {
|
|
4
4
|
voiceId: string;
|
|
5
5
|
agentTools: Tool[];
|
|
6
6
|
avatarImageUrl: string;
|
|
7
7
|
circleSize?: string;
|
|
8
|
-
isDarkTheme?: boolean;
|
|
9
8
|
children?: React.ReactNode;
|
|
10
9
|
autoStartConversation?: FirstMessages;
|
|
11
10
|
className?: string;
|
|
12
11
|
}
|
|
13
|
-
export declare function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversation, children,
|
|
12
|
+
export declare function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversation, children, circleSize, className, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
14
13
|
export {};
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { VoiceRecorder } from './EmbeddedAssistent/
|
|
3
|
+
import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecorder';
|
|
4
4
|
import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
|
|
5
5
|
import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
|
|
6
6
|
import { useChat } from '../../hooks/UseChatHook';
|
|
7
|
-
import { useRimori } from '../../
|
|
7
|
+
import { useRimori } from '../../providers/PluginProvider';
|
|
8
8
|
import { getFirstMessages } from './utils';
|
|
9
|
-
|
|
9
|
+
import { isDarkTheme } from '../../hooks/ThemeSetter';
|
|
10
|
+
export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversation, children, circleSize = '300px', className, }) {
|
|
10
11
|
const { ai, event } = useRimori();
|
|
11
12
|
const [agentReplying, setAgentReplying] = useState(false);
|
|
12
13
|
const [isProcessingMessage, setIsProcessingMessage] = useState(false);
|
|
13
14
|
const sender = useMemo(() => new MessageSender(ai.getVoice, voiceId), [voiceId]);
|
|
14
15
|
const { messages, append, isLoading, lastMessage, setMessages } = useChat(agentTools);
|
|
16
|
+
const isDarkThemeValue = useMemo(() => isDarkTheme(), []);
|
|
15
17
|
useEffect(() => {
|
|
16
18
|
console.log('messages', messages);
|
|
17
19
|
}, [messages]);
|
|
@@ -46,7 +48,7 @@ export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversat
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
}, [lastMessage, isLoading]);
|
|
49
|
-
return (_jsxs("div", { className: `md:pb-8 ${className || ''}`, children: [_jsx(CircleAudioAvatar, { width: circleSize, className: "mx-auto", imageUrl: avatarImageUrl, isDarkTheme:
|
|
51
|
+
return (_jsxs("div", { className: `md:pb-8 ${className || ''}`, children: [_jsx(CircleAudioAvatar, { width: circleSize, className: "mx-auto", imageUrl: avatarImageUrl, isDarkTheme: isDarkThemeValue }), children, _jsx(VoiceRecorder, { iconSize: "30", className: "w-16 h-16 shadow-lg rounded-full bg-gray-400 dark:bg-gray-800", disabled: agentReplying, loading: isProcessingMessage, enablePushToTalk: true, onVoiceRecorded: (message) => {
|
|
50
52
|
setAgentReplying(true);
|
|
51
53
|
append([
|
|
52
54
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { VoiceRecorder } from './
|
|
3
|
+
import { VoiceRecorder } from './VoiceRecorder';
|
|
4
4
|
import { BiSolidRightArrow } from 'react-icons/bi';
|
|
5
5
|
import { HiMiniSpeakerXMark, HiMiniSpeakerWave } from 'react-icons/hi2';
|
|
6
6
|
export function AudioInputField({ onSubmit, onAudioControl, blockSubmission = false }) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { EventBus } from '
|
|
3
|
+
import { EventBus } from '@rimori/client';
|
|
4
4
|
export function CircleAudioAvatar({ imageUrl, className, isDarkTheme = false, width = '150px', }) {
|
|
5
5
|
const canvasRef = useRef(null);
|
|
6
6
|
const currentLoudnessRef = useRef(0);
|
|
@@ -8,9 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
-
import { useRimori } from '../../../
|
|
11
|
+
import { useRimori } from '../../../providers/PluginProvider';
|
|
12
12
|
import { FaMicrophone, FaSpinner } from 'react-icons/fa6';
|
|
13
|
-
import { AudioController } from '
|
|
13
|
+
import { AudioController } from '@rimori/client';
|
|
14
14
|
import { useState, useRef, forwardRef, useImperativeHandle, useEffect } from 'react';
|
|
15
15
|
export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange, enablePushToTalk = false, }, ref) => {
|
|
16
16
|
const [isRecording, setIsRecording] = useState(false);
|
|
@@ -11,8 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { useState, useEffect } from 'react';
|
|
12
12
|
import { FaPlayCircle, FaStopCircle } from 'react-icons/fa';
|
|
13
13
|
import { useRimori } from '../../providers/PluginProvider';
|
|
14
|
-
import {
|
|
15
|
-
import { EventBus } from '../../fromRimori/EventBus';
|
|
14
|
+
import { EventBus } from '@rimori/client';
|
|
16
15
|
export const AudioPlayOptions = [0.8, 0.9, 1.0, 1.1, 1.2, 1.5];
|
|
17
16
|
let isFetchingAudio = false;
|
|
18
17
|
export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, initialSpeed = 1.0, playOnMount = false, enableSpeedAdjustment = false, }) => {
|
|
@@ -22,14 +21,11 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
|
|
|
22
21
|
const [isLoading, setIsLoading] = useState(false);
|
|
23
22
|
const { ai } = useRimori();
|
|
24
23
|
useEffect(() => {
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
EventBus.on(playListenerEvent, () => togglePlayback());
|
|
28
|
-
}, [playListenerEvent]);
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
audioUrl && setAudioUrl(null);
|
|
24
|
+
if (audioUrl)
|
|
25
|
+
setAudioUrl(null);
|
|
31
26
|
return () => {
|
|
32
|
-
|
|
27
|
+
if (audioUrl)
|
|
28
|
+
URL.revokeObjectURL(audioUrl);
|
|
33
29
|
};
|
|
34
30
|
}, [text]);
|
|
35
31
|
// Function to generate audio from text using API
|
|
@@ -69,6 +65,11 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
|
|
|
69
65
|
setIsPlaying((prev) => !prev);
|
|
70
66
|
}
|
|
71
67
|
};
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!playListenerEvent)
|
|
70
|
+
return;
|
|
71
|
+
EventBus.on(playListenerEvent, () => togglePlayback());
|
|
72
|
+
}, [playListenerEvent]);
|
|
72
73
|
useEffect(() => {
|
|
73
74
|
if (!playOnMount || isFetchingAudio)
|
|
74
75
|
return;
|
|
@@ -78,3 +79,6 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
|
|
|
78
79
|
}, [playOnMount]);
|
|
79
80
|
return (_jsx("div", { className: "group relative", children: _jsxs("div", { className: "flex flex-row items-end", children: [!hide && (_jsx("button", { className: "text-gray-500", onClick: togglePlayback, disabled: isLoading, children: isLoading ? _jsx(Spinner, {}) : isPlaying ? _jsx(FaStopCircle, { size: '25px' }) : _jsx(FaPlayCircle, { size: '25px' }) })), enableSpeedAdjustment && (_jsxs("div", { className: "ml-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-row text-sm text-gray-500", children: [_jsx("span", { className: "pr-1", children: "Speed: " }), _jsx("select", { value: speed, className: "appearance-none cursor-pointer pr-0 p-0 rounded shadow leading-tight focus:outline-none focus:bg-gray-800 focus:ring bg-transparent border-0", onChange: (e) => setSpeed(parseFloat(e.target.value)), disabled: isLoading, children: AudioPlayOptions.map((s) => (_jsx("option", { value: s, children: s }, s))) })] }))] }) }));
|
|
80
81
|
};
|
|
82
|
+
const Spinner = ({ text, className, size = '30px' }) => {
|
|
83
|
+
return (_jsxs("div", { className: 'flex items-center space-x-2 ' + className, children: [_jsxs("svg", { style: { width: size, height: size }, className: "animate-spin -ml-1 mr-3 h-5 w-5 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), text && _jsx("span", { className: "", children: text })] }));
|
|
84
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function useTheme(theme?: string | null): boolean;
|
|
2
2
|
export declare function isDarkTheme(theme?: string | null): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useTheme(theme) {
|
|
3
|
+
const [isDark, setIsDark] = useState(false);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const root = document.documentElement;
|
|
6
|
+
const nextIsDark = isDarkTheme(theme);
|
|
7
|
+
setIsDark(nextIsDark);
|
|
8
|
+
root.classList.add('dark:text-gray-200');
|
|
9
|
+
if (nextIsDark) {
|
|
10
|
+
root.setAttribute('data-theme', 'dark');
|
|
11
|
+
root.classList.add('dark', 'dark:bg-gray-950');
|
|
12
|
+
root.style.background = 'hsl(var(--background))';
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
root.removeAttribute('data-theme');
|
|
16
|
+
root.classList.remove('dark', 'dark:bg-gray-950');
|
|
17
|
+
root.style.background = '';
|
|
18
|
+
}, [theme]);
|
|
19
|
+
return isDark;
|
|
20
|
+
}
|
|
21
|
+
export function isDarkTheme(theme) {
|
|
22
|
+
// If no theme provided, try to get from URL as fallback (for standalone mode)
|
|
23
|
+
if (!theme) {
|
|
24
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
25
|
+
theme = urlParams.get('theme');
|
|
26
|
+
}
|
|
27
|
+
if (!theme || theme === 'system') {
|
|
28
|
+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
29
|
+
}
|
|
30
|
+
return theme === 'dark';
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Tool } from '
|
|
3
|
-
import { Message } from '
|
|
2
|
+
import { Tool } from '@rimori/client';
|
|
3
|
+
import { Message } from '@rimori/client';
|
|
4
4
|
export declare function useChat(tools?: Tool[]): {
|
|
5
5
|
messages: Message[];
|
|
6
6
|
append: (appendMessages: Message[]) => void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './hooks/UseChatHook';
|
|
2
|
+
export * from './providers/PluginProvider';
|
|
3
|
+
export * from './components/audio/Playbutton';
|
|
4
|
+
export * from './components/ai/EmbeddedAssistent/TTS/Player';
|
|
5
|
+
export * from './components/ai/Avatar';
|
|
6
|
+
export { FirstMessages } from './components/ai/utils';
|
|
7
|
+
export { useTranslation } from './hooks/I18nHooks';
|
|
8
|
+
export { Avatar } from './components/ai/Avatar';
|
|
9
|
+
export { VoiceRecorder } from './components/ai/EmbeddedAssistent/VoiceRecorder';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Re-export everything
|
|
2
|
+
export * from './hooks/UseChatHook';
|
|
3
|
+
export * from './providers/PluginProvider';
|
|
4
|
+
export * from './components/audio/Playbutton';
|
|
5
|
+
export * from './components/ai/EmbeddedAssistent/TTS/Player';
|
|
6
|
+
export * from './components/ai/Avatar';
|
|
7
|
+
export { useTranslation } from './hooks/I18nHooks';
|
|
8
|
+
export { Avatar } from './components/ai/Avatar';
|
|
9
|
+
export { VoiceRecorder } from './components/ai/EmbeddedAssistent/VoiceRecorder';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function useTheme(theme?: string | null): boolean;
|
|
2
2
|
export declare function isDarkTheme(theme?: string | null): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useTheme(theme) {
|
|
3
|
+
const [isDark, setIsDark] = useState(false);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const root = document.documentElement;
|
|
6
|
+
const nextIsDark = isDarkTheme(theme);
|
|
7
|
+
setIsDark(nextIsDark);
|
|
8
|
+
root.classList.add('dark:text-gray-200');
|
|
9
|
+
if (nextIsDark) {
|
|
10
|
+
root.setAttribute('data-theme', 'dark');
|
|
11
|
+
root.classList.add('dark', 'dark:bg-gray-950');
|
|
12
|
+
root.style.background = 'hsl(var(--background))';
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
root.removeAttribute('data-theme');
|
|
16
|
+
root.classList.remove('dark', 'dark:bg-gray-950');
|
|
17
|
+
root.style.background = '';
|
|
18
|
+
}, [theme]);
|
|
19
|
+
return isDark;
|
|
20
|
+
}
|
|
21
|
+
export function isDarkTheme(theme) {
|
|
22
|
+
// If no theme provided, try to get from URL as fallback (for standalone mode)
|
|
23
|
+
if (!theme) {
|
|
24
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
25
|
+
theme = urlParams.get('theme');
|
|
26
|
+
}
|
|
27
|
+
if (!theme || theme === 'system') {
|
|
28
|
+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
29
|
+
}
|
|
30
|
+
return theme === 'dark';
|
|
31
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import { RimoriClient } from '@rimori/client';
|
|
2
3
|
interface PluginProviderProps {
|
|
3
4
|
children: ReactNode;
|
|
4
5
|
pluginId: string;
|
|
@@ -7,5 +8,5 @@ interface PluginProviderProps {
|
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
10
|
export declare const PluginProvider: React.FC<PluginProviderProps>;
|
|
10
|
-
export declare const useRimori: () =>
|
|
11
|
+
export declare const useRimori: () => RimoriClient;
|
|
11
12
|
export {};
|
|
@@ -9,14 +9,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { createContext, useContext, useEffect, useState } from 'react';
|
|
12
|
-
import { RimoriClient, StandaloneClient } from '@rimori/client';
|
|
13
|
-
import ContextMenu from '../components/
|
|
12
|
+
import { EventBusHandler, RimoriClient, StandaloneClient } from '@rimori/client';
|
|
13
|
+
import ContextMenu from '../components/ContextMenu';
|
|
14
|
+
import { useTheme } from '../hooks/ThemeSetter';
|
|
14
15
|
const PluginContext = createContext(null);
|
|
15
16
|
export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
16
17
|
const [plugin, setPlugin] = useState(null);
|
|
17
18
|
const [standaloneClient, setStandaloneClient] = useState(false);
|
|
18
19
|
const [applicationMode, setApplicationMode] = useState(null);
|
|
19
20
|
const [theme, setTheme] = useState(null);
|
|
21
|
+
useTheme(theme);
|
|
20
22
|
const isSidebar = applicationMode === 'sidebar';
|
|
21
23
|
const isSettings = applicationMode === 'settings';
|
|
22
24
|
useEffect(() => {
|
|
@@ -24,12 +26,12 @@ export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
|
24
26
|
// Check if we're in an iframe context - if not, we're standalone
|
|
25
27
|
const standaloneDetected = window === window.parent;
|
|
26
28
|
if (standaloneDetected && !standaloneClient) {
|
|
27
|
-
StandaloneClient.getInstance().then((client) => {
|
|
28
|
-
client.needsLogin().then((needLogin) => setStandaloneClient(needLogin ? client : true));
|
|
29
|
+
void StandaloneClient.getInstance().then((client) => {
|
|
30
|
+
void client.needsLogin().then((needLogin) => setStandaloneClient(needLogin ? client : true));
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
if ((!standaloneDetected && !plugin) || (standaloneDetected && standaloneClient === true)) {
|
|
32
|
-
RimoriClient.getInstance(pluginId
|
|
34
|
+
void RimoriClient.getInstance(pluginId).then((client) => {
|
|
33
35
|
setPlugin(client);
|
|
34
36
|
// Get applicationMode and theme from MessageChannel query params
|
|
35
37
|
if (!standaloneDetected) {
|
|
@@ -37,10 +39,11 @@ export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
|
37
39
|
const themeParam = client.getQueryParam('rm_theme');
|
|
38
40
|
setApplicationMode(mode);
|
|
39
41
|
setTheme(themeParam);
|
|
42
|
+
client.event.emit('self.rimori.triggerInitFinished');
|
|
40
43
|
}
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
|
-
}, [pluginId, standaloneClient]);
|
|
46
|
+
}, [pluginId, standaloneClient, plugin]);
|
|
44
47
|
//route change
|
|
45
48
|
useEffect(() => {
|
|
46
49
|
if (!plugin)
|
|
@@ -59,7 +62,7 @@ export const PluginProvider = ({ children, pluginId, settings }) => {
|
|
|
59
62
|
}, 1000);
|
|
60
63
|
emitUrlChange(lastHash);
|
|
61
64
|
return () => clearInterval(interval);
|
|
62
|
-
}, [plugin]);
|
|
65
|
+
}, [plugin, isSidebar]);
|
|
63
66
|
if (standaloneClient instanceof StandaloneClient) {
|
|
64
67
|
return (_jsx(StandaloneAuth, { onLogin: (email, password) => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
68
|
if (yield standaloneClient.login(email, password))
|
|
@@ -9,11 +9,11 @@ export function triggerFullscreen(onStateChange, selector) {
|
|
|
9
9
|
const ref = document.querySelector(selector || '#root');
|
|
10
10
|
if (!isFullscreen()) {
|
|
11
11
|
// @ts-ignore
|
|
12
|
-
ref.requestFullscreen() || ref.webkitRequestFullscreen();
|
|
12
|
+
void (ref.requestFullscreen() || ref.webkitRequestFullscreen());
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
15
|
// @ts-ignore
|
|
16
|
-
document.exitFullscreen() || document.webkitExitFullscreen();
|
|
16
|
+
void (document.exitFullscreen() || document.webkitExitFullscreen());
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/react-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -20,18 +20,15 @@
|
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": "^18.0.0",
|
|
22
22
|
"react-dom": "^18.0.0",
|
|
23
|
-
"@rimori/client": "^2.
|
|
23
|
+
"@rimori/client": "^2.1.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tiptap/react": "2.10.3",
|
|
27
|
-
"@tiptap/starter-kit": "2.10.3",
|
|
28
26
|
"html2canvas": "1.4.1",
|
|
29
|
-
"react-icons": "5.4.0"
|
|
30
|
-
"tiptap-markdown": "0.8.10"
|
|
27
|
+
"react-icons": "5.4.0"
|
|
31
28
|
},
|
|
32
29
|
"devDependencies": {
|
|
33
30
|
"@eslint/js": "^9.37.0",
|
|
34
|
-
"@rimori/client": "^2.
|
|
31
|
+
"@rimori/client": "^2.1.0",
|
|
35
32
|
"eslint-config-prettier": "^10.1.8",
|
|
36
33
|
"eslint-plugin-prettier": "^5.5.4",
|
|
37
34
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { RimoriClient, MenuEntry } from '@rimori/client';
|
|
3
3
|
|
|
4
4
|
export interface Position {
|
|
5
5
|
x: number;
|
|
@@ -49,7 +49,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
|
|
|
49
49
|
setActions(actions);
|
|
50
50
|
setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
client.event.on<{ actions: MenuEntry[] }>('global.contextMenu.createActions', ({ data }) => {
|
|
53
53
|
setActions([...data.actions, ...actions]);
|
|
54
54
|
});
|
|
55
55
|
}, []);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { VoiceRecorder } from './EmbeddedAssistent/
|
|
2
|
+
import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecorder';
|
|
3
3
|
import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
|
|
4
4
|
import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
|
|
5
5
|
import { Tool } from '@rimori/client';
|
|
@@ -7,13 +7,13 @@ import { useChat } from '../../hooks/UseChatHook';
|
|
|
7
7
|
import { useRimori } from '../../providers/PluginProvider';
|
|
8
8
|
import { getFirstMessages } from './utils';
|
|
9
9
|
import { FirstMessages } from './utils';
|
|
10
|
+
import { isDarkTheme } from '../../hooks/ThemeSetter';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
voiceId: string;
|
|
13
14
|
agentTools: Tool[];
|
|
14
15
|
avatarImageUrl: string;
|
|
15
16
|
circleSize?: string;
|
|
16
|
-
isDarkTheme?: boolean;
|
|
17
17
|
children?: React.ReactNode;
|
|
18
18
|
autoStartConversation?: FirstMessages;
|
|
19
19
|
className?: string;
|
|
@@ -25,7 +25,6 @@ export function Avatar({
|
|
|
25
25
|
agentTools,
|
|
26
26
|
autoStartConversation,
|
|
27
27
|
children,
|
|
28
|
-
isDarkTheme = false,
|
|
29
28
|
circleSize = '300px',
|
|
30
29
|
className,
|
|
31
30
|
}: Props) {
|
|
@@ -34,6 +33,7 @@ export function Avatar({
|
|
|
34
33
|
const [isProcessingMessage, setIsProcessingMessage] = useState(false);
|
|
35
34
|
const sender = useMemo(() => new MessageSender(ai.getVoice, voiceId), [voiceId]);
|
|
36
35
|
const { messages, append, isLoading, lastMessage, setMessages } = useChat(agentTools);
|
|
36
|
+
const isDarkThemeValue = useMemo(() => isDarkTheme(), []);
|
|
37
37
|
|
|
38
38
|
useEffect(() => {
|
|
39
39
|
console.log('messages', messages);
|
|
@@ -74,7 +74,12 @@ export function Avatar({
|
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<div className={`md:pb-8 ${className || ''}`}>
|
|
77
|
-
<CircleAudioAvatar
|
|
77
|
+
<CircleAudioAvatar
|
|
78
|
+
width={circleSize}
|
|
79
|
+
className="mx-auto"
|
|
80
|
+
imageUrl={avatarImageUrl}
|
|
81
|
+
isDarkTheme={isDarkThemeValue}
|
|
82
|
+
/>
|
|
78
83
|
{children}
|
|
79
84
|
<VoiceRecorder
|
|
80
85
|
iconSize="30"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { VoiceRecorder } from './
|
|
2
|
+
import { VoiceRecorder } from './VoiceRecorder';
|
|
3
3
|
import { BiSolidRightArrow } from 'react-icons/bi';
|
|
4
4
|
import { HiMiniSpeakerXMark, HiMiniSpeakerWave } from 'react-icons/hi2';
|
|
5
5
|
|