@rimori/react-client 0.4.16 → 0.4.17-next.1

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.
@@ -17,5 +17,7 @@ export interface BuddyAssistantProps {
17
17
  tools?: Tool[];
18
18
  /** Set to true to disable automatic dialect from userInfo. Default: false (dialect enabled). */
19
19
  disableDialect?: boolean;
20
+ /** Show the buddy name below the avatar. Default: false. */
21
+ showName?: boolean;
20
22
  }
21
- export declare function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize, chatPlaceholder, bottomAction, className, voiceSpeed, tools, disableDialect, }: BuddyAssistantProps): JSX.Element;
23
+ export declare function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize, chatPlaceholder, bottomAction, className, voiceSpeed, tools, disableDialect, showName, }: BuddyAssistantProps): JSX.Element;
@@ -5,21 +5,19 @@ import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecorder';
5
5
  import { MessageSender } from '@rimori/client';
6
6
  import { useRimori } from '../../providers/PluginProvider';
7
7
  import { useTheme } from '../../hooks/ThemeSetter';
8
- import { HiMiniSpeakerWave, HiMiniSpeakerXMark } from 'react-icons/hi2';
9
8
  import { BiSolidRightArrow } from 'react-icons/bi';
10
9
  let idCounter = 0;
11
10
  const genId = () => `ba-${++idCounter}`;
12
- export function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize = '160px', chatPlaceholder, bottomAction, className, voiceSpeed = 1, tools, disableDialect = false, }) {
13
- var _a;
11
+ export function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize = '160px', chatPlaceholder, bottomAction, className, voiceSpeed = 1, tools, disableDialect = false, showName = false, }) {
14
12
  const { ai, event, plugin, userInfo } = useRimori();
13
+ const ttsEnabled = plugin.ttsEnabled;
15
14
  const { isDark } = useTheme(plugin.theme);
16
- const buddy = (_a = plugin.getUserInfo()) === null || _a === void 0 ? void 0 : _a.study_buddy;
15
+ const buddy = userInfo.study_buddy;
17
16
  const dialect = !disableDialect ? userInfo === null || userInfo === void 0 ? void 0 : userInfo.dialect : undefined;
18
17
  const dialectSystemSuffix = dialect
19
18
  ? `\n\nThe user is learning the regional ${dialect} dialect. Occasionally use typical regional vocabulary and expressions from this dialect to help them learn local language naturally.`
20
19
  : '';
21
20
  const dialectTtsInstruction = dialect ? `Speak with a ${dialect} accent and pronunciation.` : undefined;
22
- const [ttsEnabled, setTtsEnabled] = useState(true);
23
21
  const [chatInput, setChatInput] = useState('');
24
22
  const [messages, setMessages] = useState([]);
25
23
  const [isLoading, setIsLoading] = useState(false);
@@ -27,6 +25,11 @@ export function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize
27
25
  const ttsEnabledRef = useRef(ttsEnabled);
28
26
  useEffect(() => {
29
27
  ttsEnabledRef.current = ttsEnabled;
28
+ // Stop speaking when TTS is turned off globally
29
+ if (!ttsEnabled && isSpeaking) {
30
+ sender.stop();
31
+ setIsSpeaking(false);
32
+ }
30
33
  }, [ttsEnabled]);
31
34
  const sender = useMemo(() => { var _a; return new MessageSender((...args) => ai.getVoice(...args), (_a = buddy === null || buddy === void 0 ? void 0 : buddy.voiceId) !== null && _a !== void 0 ? _a : ''); }, [buddy === null || buddy === void 0 ? void 0 : buddy.voiceId, ai]);
32
35
  useEffect(() => {
@@ -94,21 +97,14 @@ export function BuddyAssistant({ systemPrompt, autoStartConversation, circleSize
94
97
  setMessages(newMessages);
95
98
  triggerAI(newMessages);
96
99
  };
97
- const handleToggleTts = () => {
98
- if (ttsEnabled && isSpeaking) {
99
- sender.stop();
100
- setIsSpeaking(false);
101
- }
102
- setTtsEnabled((prev) => !prev);
103
- };
104
100
  const lastAssistantMessage = [...messages].filter((m) => m.role === 'assistant').pop();
105
- return (_jsxs("div", { className: `flex flex-col items-center ${className || ''}`, children: [_jsx(CircleAudioAvatar, { width: circleSize, imageUrl: buddy.avatarUrl, isDarkTheme: isDark, className: "mx-auto" }), _jsxs("div", { className: "flex items-center gap-2 pl-10", children: [_jsx("span", { className: "text-3xl font-semibold", children: buddy.name }), _jsx("button", { type: "button", onClick: handleToggleTts, className: "p-1 rounded-md hover:bg-gray-700/50 transition-colors", title: ttsEnabled ? 'Disable voice' : 'Enable voice', children: ttsEnabled ? (_jsx(HiMiniSpeakerWave, { className: `w-5 h-5 mt-0.5 ${isSpeaking ? 'text-blue-400' : 'text-gray-300'}` })) : (_jsx(HiMiniSpeakerXMark, { className: "w-5 h-5 mt-0.5 text-gray-500" })) })] }), !ttsEnabled && (_jsx("div", { className: "w-full max-w-md rounded-xl bg-gray-800/70 px-4 py-3 text-sm text-gray-200 leading-relaxed border border-gray-700/40 mt-4", children: !(lastAssistantMessage === null || lastAssistantMessage === void 0 ? void 0 : lastAssistantMessage.content) && isLoading ? (_jsxs("span", { className: "inline-flex gap-1 py-0.5", children: [_jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.15s' } }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.3s' } })] })) : (_jsx("span", { className: "whitespace-pre-wrap", children: lastAssistantMessage === null || lastAssistantMessage === void 0 ? void 0 : lastAssistantMessage.content })) })), _jsxs("div", { className: "w-full max-w-md relative mt-4", children: [_jsx("input", { value: chatInput, onChange: (e) => setChatInput(e.target.value), onKeyDown: (e) => {
101
+ return (_jsxs("div", { className: `flex flex-col items-center ${className || ''}`, children: [_jsx(CircleAudioAvatar, { width: circleSize, imageUrl: buddy.avatarUrl, isDarkTheme: isDark, className: "mx-auto" }), showName && (_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-3xl font-semibold", children: buddy.name }) })), !ttsEnabled ? (_jsx("div", { className: "w-full rounded-xl bg-gray-200/70 dark:bg-gray-800/70 px-4 py-3 text-sm text-gray-800 dark:text-gray-200 leading-relaxed border border-gray-300/40 dark:border-gray-700/40 mt-4", children: !(lastAssistantMessage === null || lastAssistantMessage === void 0 ? void 0 : lastAssistantMessage.content) && isLoading ? (_jsxs("span", { className: "inline-flex gap-1 py-0.5", children: [_jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.15s' } }), _jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce", style: { animationDelay: '0.3s' } })] })) : (_jsx("span", { className: "whitespace-pre-wrap", children: lastAssistantMessage === null || lastAssistantMessage === void 0 ? void 0 : lastAssistantMessage.content })) })) : null, _jsxs("div", { className: 'w-full relative mt-4 ' + (ttsEnabled ? 'max-w-md' : ''), children: [_jsx("input", { value: chatInput, onChange: (e) => setChatInput(e.target.value), onKeyDown: (e) => {
106
102
  if (e.key === 'Enter' && !e.shiftKey) {
107
103
  e.preventDefault();
108
104
  sendMessage(chatInput);
109
105
  setChatInput('');
110
106
  }
111
- }, placeholder: chatPlaceholder !== null && chatPlaceholder !== void 0 ? chatPlaceholder : `Ask ${buddy.name} a question…`, disabled: isLoading, className: "w-full bg-gray-800/50 border border-gray-700 rounded-lg px-3 py-2 pr-16 text-sm text-gray-200 placeholder:text-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-60" }), _jsxs("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [_jsx(VoiceRecorder, { iconSize: "14", className: "p-1 text-gray-400 hover:text-white transition-colors", disabled: isLoading, onVoiceRecorded: (text) => sendMessage(text), onRecordingStatusChange: () => { } }), _jsx("div", { className: "w-px h-3.5 bg-gray-600" }), _jsx("button", { type: "button", onClick: () => {
107
+ }, placeholder: chatPlaceholder !== null && chatPlaceholder !== void 0 ? chatPlaceholder : `Ask ${buddy.name} a question…`, disabled: isLoading, className: "w-full bg-gray-800/50 border border-gray-700 rounded-lg px-3 py-2 pr-16 text-sm text-gray-200 placeholder:text-gray-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-60" }), _jsxs("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [_jsx(VoiceRecorder, { iconSize: "14", className: "p-1 text-gray-400 hover:text-white transition-colors", disabled: isLoading, onVoiceRecorded: (text) => sendMessage(text), onRecordingStatusChange: () => { } }), _jsx("button", { type: "button", onClick: () => {
112
108
  sendMessage(chatInput);
113
109
  setChatInput('');
114
110
  }, disabled: isLoading || !chatInput.trim(), className: "p-1 text-gray-400 hover:text-white disabled:opacity-40 transition-colors", children: _jsx(BiSolidRightArrow, { className: "w-4 h-4" }) })] })] }), bottomAction && _jsx("div", { className: "w-full max-w-md border-t border-gray-700/60 pt-3", children: bottomAction })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/react-client",
3
- "version": "0.4.16",
3
+ "version": "0.4.17-next.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,14 +18,14 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tsc && sass src/style.scss:dist/style.css",
21
- "dev": "git pull && tsc -w --preserveWatchOutput",
21
+ "dev": "tsc -w --preserveWatchOutput",
22
22
  "dev:watch": "tsc -w --preserveWatchOutput",
23
23
  "css-dev": "sass --watch src/style.scss:dist/style.css",
24
24
  "lint": "eslint . --fix",
25
25
  "format": "prettier --write ."
26
26
  },
27
27
  "peerDependencies": {
28
- "@rimori/client": "^2.5.27",
28
+ "@rimori/client": "2.5.28-next.5",
29
29
  "react": "^18.1.0",
30
30
  "react-dom": "^18.1.0"
31
31
  },
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.37.0",
52
- "@rimori/client": "^2.5.27",
52
+ "@rimori/client": "2.5.28-next.5",
53
53
  "@types/react": "^18.3.21",
54
54
  "eslint-config-prettier": "^10.1.8",
55
55
  "eslint-plugin-prettier": "^5.5.4",
@@ -4,7 +4,6 @@ import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecorder';
4
4
  import { MessageSender, Tool } from '@rimori/client';
5
5
  import { useRimori } from '../../providers/PluginProvider';
6
6
  import { useTheme } from '../../hooks/ThemeSetter';
7
- import { HiMiniSpeakerWave, HiMiniSpeakerXMark } from 'react-icons/hi2';
8
7
  import { BiSolidRightArrow } from 'react-icons/bi';
9
8
 
10
9
  type ChatMessage = { id: string; role: 'user' | 'assistant'; content: string };
@@ -27,6 +26,8 @@ export interface BuddyAssistantProps {
27
26
  tools?: Tool[];
28
27
  /** Set to true to disable automatic dialect from userInfo. Default: false (dialect enabled). */
29
28
  disableDialect?: boolean;
29
+ /** Show the buddy name below the avatar. Default: false. */
30
+ showName?: boolean;
30
31
  }
31
32
 
32
33
  let idCounter = 0;
@@ -42,17 +43,17 @@ export function BuddyAssistant({
42
43
  voiceSpeed = 1,
43
44
  tools,
44
45
  disableDialect = false,
46
+ showName = false,
45
47
  }: BuddyAssistantProps): JSX.Element {
46
48
  const { ai, event, plugin, userInfo } = useRimori();
49
+ const ttsEnabled = plugin.ttsEnabled;
47
50
  const { isDark } = useTheme(plugin.theme);
48
- const buddy = plugin.getUserInfo()?.study_buddy;
51
+ const buddy = userInfo.study_buddy;
49
52
  const dialect = !disableDialect ? userInfo?.dialect : undefined;
50
53
  const dialectSystemSuffix = dialect
51
54
  ? `\n\nThe user is learning the regional ${dialect} dialect. Occasionally use typical regional vocabulary and expressions from this dialect to help them learn local language naturally.`
52
55
  : '';
53
56
  const dialectTtsInstruction = dialect ? `Speak with a ${dialect} accent and pronunciation.` : undefined;
54
-
55
- const [ttsEnabled, setTtsEnabled] = useState(true);
56
57
  const [chatInput, setChatInput] = useState('');
57
58
  const [messages, setMessages] = useState<ChatMessage[]>([]);
58
59
  const [isLoading, setIsLoading] = useState(false);
@@ -61,6 +62,11 @@ export function BuddyAssistant({
61
62
  const ttsEnabledRef = useRef(ttsEnabled);
62
63
  useEffect(() => {
63
64
  ttsEnabledRef.current = ttsEnabled;
65
+ // Stop speaking when TTS is turned off globally
66
+ if (!ttsEnabled && isSpeaking) {
67
+ sender.stop();
68
+ setIsSpeaking(false);
69
+ }
64
70
  }, [ttsEnabled]);
65
71
 
66
72
  const sender = useMemo(
@@ -140,14 +146,6 @@ export function BuddyAssistant({
140
146
  triggerAI(newMessages);
141
147
  };
142
148
 
143
- const handleToggleTts = () => {
144
- if (ttsEnabled && isSpeaking) {
145
- sender.stop();
146
- setIsSpeaking(false);
147
- }
148
- setTtsEnabled((prev) => !prev);
149
- };
150
-
151
149
  const lastAssistantMessage = [...messages].filter((m) => m.role === 'assistant').pop();
152
150
 
153
151
  return (
@@ -155,26 +153,15 @@ export function BuddyAssistant({
155
153
  {/* Animated circle avatar */}
156
154
  <CircleAudioAvatar width={circleSize} imageUrl={buddy.avatarUrl} isDarkTheme={isDark} className="mx-auto" />
157
155
 
158
- {/* Buddy name + TTS toggle */}
159
- <div className="flex items-center gap-2 pl-10">
160
- <span className="text-3xl font-semibold">{buddy.name}</span>
161
- <button
162
- type="button"
163
- onClick={handleToggleTts}
164
- className="p-1 rounded-md hover:bg-gray-700/50 transition-colors"
165
- title={ttsEnabled ? 'Disable voice' : 'Enable voice'}
166
- >
167
- {ttsEnabled ? (
168
- <HiMiniSpeakerWave className={`w-5 h-5 mt-0.5 ${isSpeaking ? 'text-blue-400' : 'text-gray-300'}`} />
169
- ) : (
170
- <HiMiniSpeakerXMark className="w-5 h-5 mt-0.5 text-gray-500" />
171
- )}
172
- </button>
173
- </div>
156
+ {/* Buddy name (hidden by default) */}
157
+ {showName && (
158
+ <div className="flex items-center gap-2">
159
+ <span className="text-3xl font-semibold">{buddy.name}</span>
160
+ </div>
161
+ )}
174
162
 
175
- {/* Last buddy message card — only shown when TTS is disabled */}
176
- {!ttsEnabled && (
177
- <div className="w-full max-w-md rounded-xl bg-gray-800/70 px-4 py-3 text-sm text-gray-200 leading-relaxed border border-gray-700/40 mt-4">
163
+ {!ttsEnabled ? (
164
+ <div className="w-full rounded-xl bg-gray-200/70 dark:bg-gray-800/70 px-4 py-3 text-sm text-gray-800 dark:text-gray-200 leading-relaxed border border-gray-300/40 dark:border-gray-700/40 mt-4">
178
165
  {!lastAssistantMessage?.content && isLoading ? (
179
166
  <span className="inline-flex gap-1 py-0.5">
180
167
  <span className="w-1.5 h-1.5 rounded-full bg-gray-400 animate-bounce" />
@@ -191,10 +178,10 @@ export function BuddyAssistant({
191
178
  <span className="whitespace-pre-wrap">{lastAssistantMessage?.content}</span>
192
179
  )}
193
180
  </div>
194
- )}
181
+ ) : null}
195
182
 
196
183
  {/* Chat input */}
197
- <div className="w-full max-w-md relative mt-4">
184
+ <div className={'w-full relative mt-4 ' + (ttsEnabled ? 'max-w-md' : '')}>
198
185
  <input
199
186
  value={chatInput}
200
187
  onChange={(e) => setChatInput(e.target.value)}
@@ -217,7 +204,7 @@ export function BuddyAssistant({
217
204
  onVoiceRecorded={(text) => sendMessage(text)}
218
205
  onRecordingStatusChange={() => {}}
219
206
  />
220
- <div className="w-px h-3.5 bg-gray-600" />
207
+ {/* <div className="w-px h-3.5 bg-gray-600" /> */}
221
208
  <button
222
209
  type="button"
223
210
  onClick={() => {