@rimori/react-client 0.4.18-next.2 → 0.4.18-next.4

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.
@@ -11,7 +11,7 @@ export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartCo
11
11
  var _a;
12
12
  const [oralCommunication, setOralCommunication] = React.useState(true);
13
13
  const { ai: llm, event } = useRimori();
14
- const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
14
+ const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), [llm.getVoice, voiceId]);
15
15
  const { messages, append, isLoading, setMessages } = useChat();
16
16
  const lastAssistantMessage = (_a = [...messages].filter((m) => m.role === 'assistant').pop()) === null || _a === void 0 ? void 0 : _a.content;
17
17
  useEffect(() => {
@@ -27,6 +27,7 @@ export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversat
27
27
  console.log('messages', messages);
28
28
  }, [messages]);
29
29
  useEffect(() => {
30
+ // eslint-disable-next-line react-hooks/set-state-in-effect
30
31
  if (!isLoading)
31
32
  setIsProcessingMessage(false);
32
33
  }, [isLoading]);
@@ -58,6 +59,7 @@ export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversat
58
59
  sender.handleNewText(lastMessage.content, isLoading);
59
60
  if (lastMessage.toolCalls) {
60
61
  // console.log("unlocking mic", lastMessage)
62
+ // eslint-disable-next-line react-hooks/set-state-in-effect
61
63
  setAgentReplying(false);
62
64
  setIsProcessingMessage(false);
63
65
  }
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { JSX } from 'react';
2
2
  import { Tool } from '@rimori/client';
3
3
  export interface BuddyAssistantAutoStart {
4
4
  /** Pre-written assistant message shown immediately (no AI call) */
@@ -6,6 +6,32 @@ export function CircleAudioAvatar({ imageUrl, className, isDarkTheme = false, wi
6
6
  const currentLoudnessRef = useRef(0);
7
7
  const targetLoudnessRef = useRef(0);
8
8
  const animationFrameRef = useRef(null);
9
+ const draw = (ctx, canvas, image, loudness) => {
10
+ if (canvas && ctx) {
11
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
12
+ const radius = Math.min(canvas.width, canvas.height) / 3;
13
+ const centerX = canvas.width / 2;
14
+ const centerY = canvas.height / 2;
15
+ const pulseRadius = radius + loudness / 2.5;
16
+ ctx.beginPath();
17
+ ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2, true);
18
+ ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)';
19
+ ctx.lineWidth = 5;
20
+ ctx.stroke();
21
+ ctx.save();
22
+ ctx.beginPath();
23
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
24
+ ctx.closePath();
25
+ ctx.clip();
26
+ ctx.drawImage(image, centerX - radius, centerY - radius, radius * 2, radius * 2);
27
+ ctx.restore();
28
+ ctx.beginPath();
29
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
30
+ ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)';
31
+ ctx.lineWidth = 5;
32
+ ctx.stroke();
33
+ }
34
+ };
9
35
  useEffect(() => {
10
36
  const canvas = canvasRef.current;
11
37
  if (canvas) {
@@ -49,31 +75,5 @@ export function CircleAudioAvatar({ imageUrl, className, isDarkTheme = false, wi
49
75
  }
50
76
  }
51
77
  }, [imageUrl]);
52
- const draw = (ctx, canvas, image, loudness) => {
53
- if (canvas && ctx) {
54
- ctx.clearRect(0, 0, canvas.width, canvas.height);
55
- const radius = Math.min(canvas.width, canvas.height) / 3;
56
- const centerX = canvas.width / 2;
57
- const centerY = canvas.height / 2;
58
- const pulseRadius = radius + loudness / 2.5;
59
- ctx.beginPath();
60
- ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2, true);
61
- ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)';
62
- ctx.lineWidth = 5;
63
- ctx.stroke();
64
- ctx.save();
65
- ctx.beginPath();
66
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
67
- ctx.closePath();
68
- ctx.clip();
69
- ctx.drawImage(image, centerX - radius, centerY - radius, radius * 2, radius * 2);
70
- ctx.restore();
71
- ctx.beginPath();
72
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
73
- ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)';
74
- ctx.lineWidth = 5;
75
- ctx.stroke();
76
- }
77
- };
78
78
  return _jsx("canvas", { ref: canvasRef, className: className, width: 500, height: 500, style: { width } });
79
79
  }
@@ -23,6 +23,7 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
23
23
  const audioRef = useRef(null);
24
24
  const eventBusListenerRef = useRef(null);
25
25
  useEffect(() => {
26
+ // eslint-disable-next-line react-hooks/set-state-in-effect
26
27
  if (audioUrl)
27
28
  setAudioUrl(null);
28
29
  return () => {
@@ -128,6 +129,7 @@ export const AudioPlayer = ({ text, voice, language, hide, playListenerEvent, in
128
129
  return;
129
130
  isFetchingAudio = true;
130
131
  // console.log("playOnMount", playOnMount);
132
+ // eslint-disable-next-line react-hooks/set-state-in-effect
131
133
  togglePlayback();
132
134
  }, [playOnMount]);
133
135
  return (_jsx("div", { className: "group relative", children: _jsxs("div", { className: "flex flex-row items-end", children: [!hide && (_jsx("button", { className: "text-gray-400", onClick: togglePlayback, disabled: isLoading, children: isLoading ? (_jsx(Spinner, { size: size })) : isPlaying ? (_jsx(FaStopCircle, { size: size })) : (_jsx(FaPlayCircle, { size: size })) })), 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))) })] }))] }) }));
@@ -78,6 +78,7 @@ const InlinePanel = ({ panel, onClose, editor, onUpdate, labels, onTransform, is
78
78
  useEffect(() => {
79
79
  if (panel === 'link') {
80
80
  const href = editor.getAttributes('link').href;
81
+ // eslint-disable-next-line react-hooks/set-state-in-effect
81
82
  setValue(typeof href === 'string' ? href : 'https://');
82
83
  }
83
84
  else {
package/dist/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export { VoiceRecorder } from './components/ai/EmbeddedAssistent/VoiceRecorder';
12
12
  export { MarkdownEditor } from './components/editor/MarkdownEditor';
13
13
  export type { MarkdownEditorProps, EditorLabels } from './components/editor/MarkdownEditor';
14
14
  export { extractImageUrls } from './components/editor/imageUtils';
15
+ export { injectFederationCss } from './utils/injectFederationCss';
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export { BuddyAssistant } from './components/ai/BuddyAssistant';
10
10
  export { VoiceRecorder } from './components/ai/EmbeddedAssistent/VoiceRecorder';
11
11
  export { MarkdownEditor } from './components/editor/MarkdownEditor';
12
12
  export { extractImageUrls } from './components/editor/imageUtils';
13
+ export { injectFederationCss } from './utils/injectFederationCss';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Inject a plugin's Tailwind CSS into the document when a federated module loads.
3
+ * Inserts BEFORE existing stylesheets so rimori-main's responsive classes (e.g. md:hidden)
4
+ * keep higher cascade priority and are not overridden by base utility classes from the plugin.
5
+ *
6
+ * @param pluginId - Unique plugin identifier used as the <style> element id (e.g. 'rimori-plugin-translator')
7
+ * @param cssText - The inlined CSS string (imported via `?inline`)
8
+ */
9
+ export declare function injectFederationCss(pluginId: string, cssText: string): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Inject a plugin's Tailwind CSS into the document when a federated module loads.
3
+ * Inserts BEFORE existing stylesheets so rimori-main's responsive classes (e.g. md:hidden)
4
+ * keep higher cascade priority and are not overridden by base utility classes from the plugin.
5
+ *
6
+ * @param pluginId - Unique plugin identifier used as the <style> element id (e.g. 'rimori-plugin-translator')
7
+ * @param cssText - The inlined CSS string (imported via `?inline`)
8
+ */
9
+ export function injectFederationCss(pluginId, cssText) {
10
+ if (typeof document === 'undefined' || !cssText)
11
+ return;
12
+ const id = `${pluginId}-css`;
13
+ if (document.getElementById(id))
14
+ return;
15
+ const style = document.createElement('style');
16
+ style.id = id;
17
+ style.textContent = cssText;
18
+ const firstStyle = document.head.querySelector('style, link[rel="stylesheet"]');
19
+ if (firstStyle) {
20
+ document.head.insertBefore(style, firstStyle);
21
+ }
22
+ else {
23
+ document.head.appendChild(style);
24
+ }
25
+ }
package/eslint.config.js CHANGED
@@ -19,6 +19,9 @@ export default [
19
19
  ...globals.node,
20
20
  },
21
21
  sourceType: 'module',
22
+ parserOptions: {
23
+ tsconfigRootDir: import.meta.dirname,
24
+ },
22
25
  },
23
26
  plugins: {
24
27
  'react-hooks': reactHooks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/react-client",
3
- "version": "0.4.18-next.2",
3
+ "version": "0.4.18-next.4",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "format": "prettier --write ."
26
26
  },
27
27
  "peerDependencies": {
28
- "@rimori/client": "2.5.29-next.1",
29
- "react": "^18.1.0",
30
- "react-dom": "^18.1.0"
28
+ "@rimori/client": "2.5.29-next.3",
29
+ "react": "^19.0.0",
30
+ "react-dom": "^19.0.0"
31
31
  },
32
32
  "dependencies": {
33
33
  "@tiptap/core": "^2.26.1",
@@ -48,8 +48,8 @@
48
48
  "tiptap-markdown": "^0.8.10"
49
49
  },
50
50
  "devDependencies": {
51
- "@rimori/client": "2.5.29-next.1",
52
- "@types/react": "^18.3.21",
51
+ "@rimori/client": "2.5.29-next.3",
52
+ "@types/react": "^19.0.0",
53
53
  "eslint-config-prettier": "^10.1.8",
54
54
  "eslint-plugin-react-hooks": "^7.0.0",
55
55
  "form-data": "^4.0.2",
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { JSX, useState, useEffect, useRef } from 'react';
2
2
  import { RimoriClient, MenuEntry } from '@rimori/client';
3
3
 
4
4
  export interface Position {
@@ -17,7 +17,7 @@ interface Props {
17
17
  export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }: Props) {
18
18
  const [oralCommunication, setOralCommunication] = React.useState(true);
19
19
  const { ai: llm, event } = useRimori();
20
- const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
20
+ const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), [llm.getVoice, voiceId]);
21
21
  const { messages, append, isLoading, setMessages } = useChat();
22
22
 
23
23
  const lastAssistantMessage = [...messages].filter((m) => m.role === 'assistant').pop()?.content;
@@ -61,6 +61,7 @@ export function Avatar({
61
61
  }, [messages]);
62
62
 
63
63
  useEffect(() => {
64
+ // eslint-disable-next-line react-hooks/set-state-in-effect
64
65
  if (!isLoading) setIsProcessingMessage(false);
65
66
  }, [isLoading]);
66
67
 
@@ -94,7 +95,9 @@ export function Avatar({
94
95
  sender.handleNewText(lastMessage.content, isLoading);
95
96
  if (lastMessage.toolCalls) {
96
97
  // console.log("unlocking mic", lastMessage)
98
+ // eslint-disable-next-line react-hooks/set-state-in-effect
97
99
  setAgentReplying(false);
100
+
98
101
  setIsProcessingMessage(false);
99
102
  }
100
103
  }
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useMemo, useRef } from 'react';
1
+ import React, { JSX, useState, useEffect, useMemo, useRef } from 'react';
2
2
  import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
3
3
  import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecorder';
4
4
  import { MessageSender, Tool } from '@rimori/client';
@@ -19,6 +19,41 @@ export function CircleAudioAvatar({
19
19
  const targetLoudnessRef = useRef(0);
20
20
  const animationFrameRef = useRef<number | null>(null);
21
21
 
22
+ const draw = (
23
+ ctx: CanvasRenderingContext2D,
24
+ canvas: HTMLCanvasElement,
25
+ image: HTMLImageElement,
26
+ loudness: number,
27
+ ) => {
28
+ if (canvas && ctx) {
29
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
30
+
31
+ const radius = Math.min(canvas.width, canvas.height) / 3;
32
+ const centerX = canvas.width / 2;
33
+ const centerY = canvas.height / 2;
34
+ const pulseRadius = radius + loudness / 2.5;
35
+ ctx.beginPath();
36
+ ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2, true);
37
+ ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)';
38
+ ctx.lineWidth = 5;
39
+ ctx.stroke();
40
+
41
+ ctx.save();
42
+ ctx.beginPath();
43
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
44
+ ctx.closePath();
45
+ ctx.clip();
46
+ ctx.drawImage(image, centerX - radius, centerY - radius, radius * 2, radius * 2);
47
+ ctx.restore();
48
+
49
+ ctx.beginPath();
50
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
51
+ ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)';
52
+ ctx.lineWidth = 5;
53
+ ctx.stroke();
54
+ }
55
+ };
56
+
22
57
  useEffect(() => {
23
58
  const canvas = canvasRef.current;
24
59
  if (canvas) {
@@ -68,40 +103,5 @@ export function CircleAudioAvatar({
68
103
  }
69
104
  }, [imageUrl]);
70
105
 
71
- const draw = (
72
- ctx: CanvasRenderingContext2D,
73
- canvas: HTMLCanvasElement,
74
- image: HTMLImageElement,
75
- loudness: number,
76
- ) => {
77
- if (canvas && ctx) {
78
- ctx.clearRect(0, 0, canvas.width, canvas.height);
79
-
80
- const radius = Math.min(canvas.width, canvas.height) / 3;
81
- const centerX = canvas.width / 2;
82
- const centerY = canvas.height / 2;
83
- const pulseRadius = radius + loudness / 2.5;
84
- ctx.beginPath();
85
- ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2, true);
86
- ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.5)';
87
- ctx.lineWidth = 5;
88
- ctx.stroke();
89
-
90
- ctx.save();
91
- ctx.beginPath();
92
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
93
- ctx.closePath();
94
- ctx.clip();
95
- ctx.drawImage(image, centerX - radius, centerY - radius, radius * 2, radius * 2);
96
- ctx.restore();
97
-
98
- ctx.beginPath();
99
- ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, true);
100
- ctx.strokeStyle = isDarkTheme ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)';
101
- ctx.lineWidth = 5;
102
- ctx.stroke();
103
- }
104
- };
105
-
106
106
  return <canvas ref={canvasRef} className={className} width={500} height={500} style={{ width }} />;
107
107
  }
@@ -48,6 +48,7 @@ export const AudioPlayer: React.FC<AudioPlayerProps> = ({
48
48
  const eventBusListenerRef = useRef<{ off: () => void } | null>(null);
49
49
 
50
50
  useEffect(() => {
51
+ // eslint-disable-next-line react-hooks/set-state-in-effect
51
52
  if (audioUrl) setAudioUrl(null);
52
53
  return () => {
53
54
  if (audioUrl) URL.revokeObjectURL(audioUrl);
@@ -170,6 +171,7 @@ export const AudioPlayer: React.FC<AudioPlayerProps> = ({
170
171
  if (!playOnMount || isFetchingAudio) return;
171
172
  isFetchingAudio = true;
172
173
  // console.log("playOnMount", playOnMount);
174
+ // eslint-disable-next-line react-hooks/set-state-in-effect
173
175
  togglePlayback();
174
176
  }, [playOnMount]);
175
177
 
@@ -153,6 +153,7 @@ const InlinePanel = ({
153
153
  useEffect(() => {
154
154
  if (panel === 'link') {
155
155
  const href = editor.getAttributes('link').href as string | undefined;
156
+ // eslint-disable-next-line react-hooks/set-state-in-effect
156
157
  setValue(typeof href === 'string' ? href : 'https://');
157
158
  } else {
158
159
  setValue('');
package/src/index.ts CHANGED
@@ -13,3 +13,4 @@ export { VoiceRecorder } from './components/ai/EmbeddedAssistent/VoiceRecorder';
13
13
  export { MarkdownEditor } from './components/editor/MarkdownEditor';
14
14
  export type { MarkdownEditorProps, EditorLabels } from './components/editor/MarkdownEditor';
15
15
  export { extractImageUrls } from './components/editor/imageUtils';
16
+ export { injectFederationCss } from './utils/injectFederationCss';
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, ReactNode, useEffect, useState } from 'react';
1
+ import React, { createContext, useContext, JSX, ReactNode, useEffect, useState } from 'react';
2
2
  import { EventBusHandler, RimoriClient, StandaloneClient } from '@rimori/client';
3
3
  import type { UserInfo } from '@rimori/client';
4
4
  import posthog from 'posthog-js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Inject a plugin's Tailwind CSS into the document when a federated module loads.
3
+ * Inserts BEFORE existing stylesheets so rimori-main's responsive classes (e.g. md:hidden)
4
+ * keep higher cascade priority and are not overridden by base utility classes from the plugin.
5
+ *
6
+ * @param pluginId - Unique plugin identifier used as the <style> element id (e.g. 'rimori-plugin-translator')
7
+ * @param cssText - The inlined CSS string (imported via `?inline`)
8
+ */
9
+ export function injectFederationCss(pluginId: string, cssText: string): void {
10
+ if (typeof document === 'undefined' || !cssText) return;
11
+
12
+ const id = `${pluginId}-css`;
13
+ if (document.getElementById(id)) return;
14
+
15
+ const style = document.createElement('style');
16
+ style.id = id;
17
+ style.textContent = cssText;
18
+
19
+ const firstStyle = document.head.querySelector('style, link[rel="stylesheet"]');
20
+ if (firstStyle) {
21
+ document.head.insertBefore(style, firstStyle);
22
+ } else {
23
+ document.head.appendChild(style);
24
+ }
25
+ }