@rimori/client 1.3.1 → 1.4.3

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.
Files changed (148) hide show
  1. package/.prettierignore +35 -0
  2. package/README.md +77 -71
  3. package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
  4. package/dist/cli/scripts/init/dev-registration.js +4 -4
  5. package/dist/cli/scripts/init/main.js +1 -1
  6. package/dist/cli/scripts/init/package-setup.d.ts +1 -1
  7. package/dist/cli/scripts/init/package-setup.js +3 -3
  8. package/dist/cli/scripts/init/router-transformer.js +19 -12
  9. package/dist/cli/scripts/init/vite-config.d.ts +2 -2
  10. package/dist/cli/scripts/init/vite-config.js +2 -2
  11. package/dist/cli/scripts/release/release-config-upload.js +9 -9
  12. package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
  13. package/dist/cli/scripts/release/release-db-update.js +9 -9
  14. package/dist/cli/scripts/release/release-file-upload.js +2 -2
  15. package/dist/cli/scripts/release/release.js +2 -2
  16. package/dist/cli/types/DatabaseTypes.d.ts +2 -2
  17. package/dist/components/CRUDModal.d.ts +1 -1
  18. package/dist/components/CRUDModal.js +3 -3
  19. package/dist/components/MarkdownEditor.js +16 -16
  20. package/dist/components/Spinner.js +2 -2
  21. package/dist/components/ai/Assistant.js +7 -8
  22. package/dist/components/ai/Avatar.d.ts +2 -2
  23. package/dist/components/ai/Avatar.js +14 -7
  24. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
  25. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
  26. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
  27. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
  28. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
  29. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +1 -1
  30. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
  31. package/dist/components/audio/Playbutton.js +10 -7
  32. package/dist/components/components/ContextMenu.d.ts +1 -1
  33. package/dist/components/components/ContextMenu.js +19 -16
  34. package/dist/components.d.ts +10 -10
  35. package/dist/components.js +10 -10
  36. package/dist/core/controller/AIController.d.ts +2 -2
  37. package/dist/core/controller/AIController.js +20 -18
  38. package/dist/core/controller/ExerciseController.d.ts +52 -0
  39. package/dist/core/controller/ExerciseController.js +73 -0
  40. package/dist/core/controller/ObjectController.js +5 -5
  41. package/dist/core/controller/SettingsController.d.ts +22 -7
  42. package/dist/core/controller/SettingsController.js +73 -8
  43. package/dist/core/controller/SharedContentController.d.ts +3 -3
  44. package/dist/core/controller/SharedContentController.js +38 -20
  45. package/dist/core/controller/VoiceController.js +6 -4
  46. package/dist/core/core.d.ts +15 -14
  47. package/dist/core/core.js +7 -7
  48. package/dist/fromRimori/EventBus.js +23 -23
  49. package/dist/fromRimori/PluginTypes.d.ts +4 -4
  50. package/dist/hooks/UseChatHook.d.ts +3 -3
  51. package/dist/hooks/UseChatHook.js +9 -3
  52. package/dist/index.d.ts +10 -10
  53. package/dist/index.js +9 -9
  54. package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
  55. package/dist/plugin/AccomplishmentHandler.js +31 -27
  56. package/dist/plugin/AudioController.d.ts +1 -1
  57. package/dist/plugin/AudioController.js +6 -6
  58. package/dist/plugin/Logger.d.ts +5 -0
  59. package/dist/plugin/Logger.js +65 -13
  60. package/dist/plugin/PluginController.d.ts +7 -1
  61. package/dist/plugin/PluginController.js +32 -27
  62. package/dist/plugin/RimoriClient.d.ts +39 -14
  63. package/dist/plugin/RimoriClient.js +60 -31
  64. package/dist/plugin/StandaloneClient.d.ts +1 -1
  65. package/dist/plugin/StandaloneClient.js +35 -16
  66. package/dist/plugin/ThemeSetter.js +4 -4
  67. package/dist/providers/PluginProvider.js +44 -14
  68. package/dist/utils/Language.js +57 -57
  69. package/dist/utils/PluginUtils.js +3 -3
  70. package/dist/utils/difficultyConverter.d.ts +1 -1
  71. package/dist/utils/difficultyConverter.js +1 -1
  72. package/dist/utils/endpoint.js +2 -2
  73. package/dist/worker/WorkerSetup.d.ts +1 -1
  74. package/dist/worker/WorkerSetup.js +6 -6
  75. package/eslint.config.js +53 -0
  76. package/example/docs/devdocs.md +50 -40
  77. package/example/docs/overview.md +1 -1
  78. package/example/docs/userdocs.md +4 -1
  79. package/example/rimori.config.ts +51 -49
  80. package/example/worker/vite.config.ts +3 -3
  81. package/example/worker/worker.ts +2 -2
  82. package/package.json +17 -4
  83. package/prettier.config.js +8 -0
  84. package/src/cli/scripts/init/dev-registration.ts +5 -8
  85. package/src/cli/scripts/init/env-setup.ts +1 -1
  86. package/src/cli/scripts/init/file-operations.ts +1 -1
  87. package/src/cli/scripts/init/html-cleaner.ts +2 -5
  88. package/src/cli/scripts/init/main.ts +16 -13
  89. package/src/cli/scripts/init/package-setup.ts +11 -15
  90. package/src/cli/scripts/init/router-transformer.ts +40 -37
  91. package/src/cli/scripts/init/tailwind-config.ts +17 -26
  92. package/src/cli/scripts/init/vite-config.ts +3 -3
  93. package/src/cli/scripts/release/release-config-upload.ts +11 -11
  94. package/src/cli/scripts/release/release-db-update.ts +12 -12
  95. package/src/cli/scripts/release/release-file-upload.ts +3 -3
  96. package/src/cli/scripts/release/release.ts +4 -4
  97. package/src/cli/types/DatabaseTypes.ts +7 -8
  98. package/src/components/CRUDModal.tsx +64 -48
  99. package/src/components/MarkdownEditor.tsx +58 -27
  100. package/src/components/Spinner.tsx +24 -17
  101. package/src/components/ai/Assistant.tsx +70 -70
  102. package/src/components/ai/Avatar.tsx +20 -16
  103. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
  104. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
  105. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
  106. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +177 -178
  107. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
  108. package/src/components/ai/utils.ts +4 -4
  109. package/src/components/audio/Playbutton.tsx +101 -93
  110. package/src/components/components/ContextMenu.tsx +47 -35
  111. package/src/components.ts +10 -10
  112. package/src/core/controller/AIController.ts +62 -50
  113. package/src/core/controller/ExerciseController.ts +98 -0
  114. package/src/core/controller/ObjectController.ts +15 -10
  115. package/src/core/controller/SettingsController.ts +89 -16
  116. package/src/core/controller/SharedContentController.ts +80 -44
  117. package/src/core/controller/VoiceController.ts +10 -8
  118. package/src/core/core.ts +15 -15
  119. package/src/fromRimori/EventBus.ts +76 -47
  120. package/src/fromRimori/PluginTypes.ts +26 -17
  121. package/src/fromRimori/readme.md +2 -2
  122. package/src/hooks/UseChatHook.ts +25 -15
  123. package/src/index.ts +10 -10
  124. package/src/plugin/AccomplishmentHandler.ts +53 -35
  125. package/src/plugin/AudioController.ts +18 -12
  126. package/src/plugin/Logger.ts +77 -19
  127. package/src/plugin/PluginController.ts +60 -44
  128. package/src/plugin/RimoriClient.ts +133 -69
  129. package/src/plugin/StandaloneClient.ts +51 -24
  130. package/src/plugin/ThemeSetter.ts +5 -5
  131. package/src/providers/PluginProvider.tsx +90 -36
  132. package/src/style.scss +3 -3
  133. package/src/utils/Language.ts +58 -58
  134. package/src/utils/PluginUtils.ts +16 -20
  135. package/src/utils/difficultyConverter.ts +2 -2
  136. package/src/utils/endpoint.ts +3 -2
  137. package/src/worker/WorkerSetup.ts +8 -9
  138. package/tsconfig.json +2 -4
  139. package/dist/components/LoggerExample.d.ts +0 -6
  140. package/dist/components/LoggerExample.js +0 -79
  141. package/dist/core/controller/AudioController.d.ts +0 -0
  142. package/dist/core/controller/AudioController.js +0 -1
  143. package/dist/hooks/UseLogger.d.ts +0 -30
  144. package/dist/hooks/UseLogger.js +0 -122
  145. package/dist/plugin/LoggerExample.d.ts +0 -16
  146. package/dist/plugin/LoggerExample.js +0 -140
  147. package/dist/utils/audioFormats.d.ts +0 -26
  148. package/dist/utils/audioFormats.js +0 -67
@@ -13,102 +13,117 @@ interface Props {
13
13
  onVoiceRecorded: (message: string) => void;
14
14
  }
15
15
 
16
- export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange, enablePushToTalk = false }: Props, ref) => {
17
- const [isRecording, setIsRecording] = useState(false);
18
- const [internalIsProcessing, setInternalIsProcessing] = useState(false);
19
- const audioControllerRef = useRef<AudioController | null>(null);
20
- const { ai, plugin } = useRimori();
21
-
22
- // Ref for latest onVoiceRecorded callback
23
- const onVoiceRecordedRef = useRef(onVoiceRecorded);
24
- useEffect(() => {
25
- onVoiceRecordedRef.current = onVoiceRecorded;
26
- }, [onVoiceRecorded]);
27
-
28
- const startRecording = async () => {
29
- try {
30
- if (!audioControllerRef.current) {
31
- audioControllerRef.current = new AudioController(plugin.pluginId);
32
- }
33
-
34
- await audioControllerRef.current.startRecording();
35
- setIsRecording(true);
36
- onRecordingStatusChange(true);
37
- } catch (error) {
38
- console.error('Failed to start recording:', error);
39
- // Handle permission denied or other errors
40
- }
41
- };
42
-
43
-
44
-
45
- const stopRecording = async () => {
46
- try {
47
- if (audioControllerRef.current && isRecording) {
48
- const audioResult = await audioControllerRef.current.stopRecording();
49
- // console.log("audioResult: ", audioResult);
50
-
51
- setInternalIsProcessing(true);
52
-
53
- // Play the recorded audio from the Blob
54
- // const blobUrl = URL.createObjectURL(audioResult.recording);
55
- // const audioRef = new Audio(blobUrl);
56
- // audioRef.onended = () => URL.revokeObjectURL(blobUrl);
57
- // audioRef.play().catch((e) => console.error('Playback error:', e));
58
-
59
- // console.log("audioBlob: ", audioResult.recording);
60
- const text = await ai.getTextFromVoice(audioResult.recording);
61
- // console.log("stt result", text);
62
- // throw new Error("test");
63
- setInternalIsProcessing(false);
64
- onVoiceRecordedRef.current(text);
65
- }
66
- } catch (error) {
67
- console.error('Failed to stop recording:', error);
68
- } finally {
69
- setIsRecording(false);
70
- onRecordingStatusChange(false);
71
- }
72
- };
73
-
74
- useImperativeHandle(ref, () => ({
75
- startRecording,
76
- stopRecording,
77
- }));
78
-
79
- // push to talk feature
80
- const spacePressedRef = useRef(false);
81
-
82
- useEffect(() => {
83
- if (!enablePushToTalk) return;
84
-
85
- const handleKeyDown = async (event: KeyboardEvent) => {
86
- if (event.code === 'Space' && !spacePressedRef.current) {
87
- spacePressedRef.current = true;
88
- await startRecording();
16
+ export const VoiceRecorder = forwardRef(
17
+ (
18
+ {
19
+ onVoiceRecorded,
20
+ iconSize,
21
+ className,
22
+ disabled,
23
+ loading,
24
+ onRecordingStatusChange,
25
+ enablePushToTalk = false,
26
+ }: Props,
27
+ ref,
28
+ ) => {
29
+ const [isRecording, setIsRecording] = useState(false);
30
+ const [internalIsProcessing, setInternalIsProcessing] = useState(false);
31
+ const audioControllerRef = useRef<AudioController | null>(null);
32
+ const { ai, plugin } = useRimori();
33
+
34
+ // Ref for latest onVoiceRecorded callback
35
+ const onVoiceRecordedRef = useRef(onVoiceRecorded);
36
+ useEffect(() => {
37
+ onVoiceRecordedRef.current = onVoiceRecorded;
38
+ }, [onVoiceRecorded]);
39
+
40
+ const startRecording = async () => {
41
+ try {
42
+ if (!audioControllerRef.current) {
43
+ audioControllerRef.current = new AudioController(plugin.pluginId);
44
+ }
45
+
46
+ await audioControllerRef.current.startRecording();
47
+ setIsRecording(true);
48
+ onRecordingStatusChange(true);
49
+ } catch (error) {
50
+ console.error('Failed to start recording:', error);
51
+ // Handle permission denied or other errors
89
52
  }
90
53
  };
91
- const handleKeyUp = (event: KeyboardEvent) => {
92
- if (event.code === 'Space' && spacePressedRef.current) {
93
- spacePressedRef.current = false;
94
- stopRecording();
54
+
55
+ const stopRecording = async () => {
56
+ try {
57
+ if (audioControllerRef.current && isRecording) {
58
+ const audioResult = await audioControllerRef.current.stopRecording();
59
+ // console.log("audioResult: ", audioResult);
60
+
61
+ setInternalIsProcessing(true);
62
+
63
+ // Play the recorded audio from the Blob
64
+ // const blobUrl = URL.createObjectURL(audioResult.recording);
65
+ // const audioRef = new Audio(blobUrl);
66
+ // audioRef.onended = () => URL.revokeObjectURL(blobUrl);
67
+ // audioRef.play().catch((e) => console.error('Playback error:', e));
68
+
69
+ // console.log("audioBlob: ", audioResult.recording);
70
+ const text = await ai.getTextFromVoice(audioResult.recording);
71
+ // console.log("stt result", text);
72
+ // throw new Error("test");
73
+ setInternalIsProcessing(false);
74
+ onVoiceRecordedRef.current(text);
75
+ }
76
+ } catch (error) {
77
+ console.error('Failed to stop recording:', error);
78
+ } finally {
79
+ setIsRecording(false);
80
+ onRecordingStatusChange(false);
95
81
  }
96
82
  };
97
- window.addEventListener('keydown', handleKeyDown);
98
- window.addEventListener('keyup', handleKeyUp);
99
- return () => {
100
- window.removeEventListener('keydown', handleKeyDown);
101
- window.removeEventListener('keyup', handleKeyUp);
102
- };
103
- }, [enablePushToTalk]);
104
83
 
105
- return (
106
- <button className={"flex flex-row justify-center items-center rounded-full mx-auto disabled:opacity-50 " + className}
107
- onClick={isRecording ? stopRecording : startRecording}
108
- disabled={disabled || loading || internalIsProcessing}>
109
- {loading || internalIsProcessing ? <FaSpinner className="animate-spin" /> :
110
- <FaMicrophone size={iconSize} className={(isRecording ? "text-red-600" : "")} />
111
- }
112
- </button>
113
- );
114
- });
84
+ useImperativeHandle(ref, () => ({
85
+ startRecording,
86
+ stopRecording,
87
+ }));
88
+
89
+ // push to talk feature
90
+ const spacePressedRef = useRef(false);
91
+
92
+ useEffect(() => {
93
+ if (!enablePushToTalk) return;
94
+
95
+ const handleKeyDown = async (event: KeyboardEvent) => {
96
+ if (event.code === 'Space' && !spacePressedRef.current) {
97
+ spacePressedRef.current = true;
98
+ await startRecording();
99
+ }
100
+ };
101
+ const handleKeyUp = (event: KeyboardEvent) => {
102
+ if (event.code === 'Space' && spacePressedRef.current) {
103
+ spacePressedRef.current = false;
104
+ stopRecording();
105
+ }
106
+ };
107
+ window.addEventListener('keydown', handleKeyDown);
108
+ window.addEventListener('keyup', handleKeyUp);
109
+ return () => {
110
+ window.removeEventListener('keydown', handleKeyDown);
111
+ window.removeEventListener('keyup', handleKeyUp);
112
+ };
113
+ }, [enablePushToTalk]);
114
+
115
+ return (
116
+ <button
117
+ className={'flex flex-row justify-center items-center rounded-full mx-auto disabled:opacity-50 ' + className}
118
+ onClick={isRecording ? stopRecording : startRecording}
119
+ disabled={disabled || loading || internalIsProcessing}
120
+ >
121
+ {loading || internalIsProcessing ? (
122
+ <FaSpinner className="animate-spin" />
123
+ ) : (
124
+ <FaMicrophone size={iconSize} className={isRecording ? 'text-red-600' : ''} />
125
+ )}
126
+ </button>
127
+ );
128
+ },
129
+ );
@@ -8,14 +8,14 @@ export function getFirstMessages(instructions: FirstMessages): any[] {
8
8
  const messages = [];
9
9
 
10
10
  if (instructions.instructions) {
11
- messages.push({ id: '1', role: 'system', content: instructions.instructions });
11
+ messages.push({ id: '1', role: 'system', content: instructions.instructions });
12
12
  }
13
13
  if (instructions.userMessage) {
14
- messages.push({ id: '2', role: 'user', content: instructions.userMessage });
14
+ messages.push({ id: '2', role: 'user', content: instructions.userMessage });
15
15
  }
16
16
  if (instructions.assistantMessage) {
17
- messages.push({ id: '3', role: 'assistant', content: instructions.assistantMessage });
17
+ messages.push({ id: '3', role: 'assistant', content: instructions.assistantMessage });
18
18
  }
19
19
 
20
20
  return messages;
21
- }
21
+ }
@@ -1,18 +1,18 @@
1
1
  import React, { useState, useEffect } from 'react';
2
- import { FaPlayCircle, FaStopCircle } from "react-icons/fa";
3
- import { useRimori } from "../../providers/PluginProvider";
2
+ import { FaPlayCircle, FaStopCircle } from 'react-icons/fa';
3
+ import { useRimori } from '../../providers/PluginProvider';
4
4
  import { Spinner } from '../Spinner';
5
5
  import { EventBus } from '../../fromRimori/EventBus';
6
6
 
7
7
  type AudioPlayerProps = {
8
- text: string;
9
- voice?: string;
10
- language?: string;
11
- hide?: boolean;
12
- playOnMount?: boolean;
13
- initialSpeed?: number;
14
- enableSpeedAdjustment?: boolean;
15
- playListenerEvent?: string;
8
+ text: string;
9
+ voice?: string;
10
+ language?: string;
11
+ hide?: boolean;
12
+ playOnMount?: boolean;
13
+ initialSpeed?: number;
14
+ enableSpeedAdjustment?: boolean;
15
+ playListenerEvent?: string;
16
16
  };
17
17
 
18
18
  export const AudioPlayOptions = [0.8, 0.9, 1.0, 1.1, 1.2, 1.5];
@@ -21,98 +21,106 @@ export type AudioPlayOptionType = 0.8 | 0.9 | 1.0 | 1.1 | 1.2 | 1.5;
21
21
  let isFetchingAudio = false;
22
22
 
23
23
  export const AudioPlayer: React.FC<AudioPlayerProps> = ({
24
- text,
25
- voice,
26
- language,
27
- hide,
28
- playListenerEvent,
29
- initialSpeed = 1.0,
30
- playOnMount = false,
31
- enableSpeedAdjustment = false,
24
+ text,
25
+ voice,
26
+ language,
27
+ hide,
28
+ playListenerEvent,
29
+ initialSpeed = 1.0,
30
+ playOnMount = false,
31
+ enableSpeedAdjustment = false,
32
32
  }) => {
33
- const [audioUrl, setAudioUrl] = useState<string | null>(null);
34
- const [speed, setSpeed] = useState(initialSpeed);
35
- const [isPlaying, setIsPlaying] = useState(false);
36
- const [isLoading, setIsLoading] = useState(false);
37
- const { ai } = useRimori();
33
+ const [audioUrl, setAudioUrl] = useState<string | null>(null);
34
+ const [speed, setSpeed] = useState(initialSpeed);
35
+ const [isPlaying, setIsPlaying] = useState(false);
36
+ const [isLoading, setIsLoading] = useState(false);
37
+ const { ai } = useRimori();
38
38
 
39
- useEffect(() => {
40
- if (!playListenerEvent) return;
41
- EventBus.on(playListenerEvent, () => togglePlayback());
42
- }, [playListenerEvent]);
39
+ useEffect(() => {
40
+ if (!playListenerEvent) return;
41
+ EventBus.on(playListenerEvent, () => togglePlayback());
42
+ }, [playListenerEvent]);
43
43
 
44
- useEffect(() => {
45
- audioUrl && setAudioUrl(null);
46
- return () => {
47
- audioUrl && URL.revokeObjectURL(audioUrl);
48
- }
49
- }, [text]);
50
-
51
- // Function to generate audio from text using API
52
- const generateAudio = async () => {
53
- setIsLoading(true);
54
-
55
- const blob = await ai.getVoice(text, voice || (language ? "aws_default" : "openai_alloy"), 1, language);
56
- setAudioUrl(URL.createObjectURL(blob));
57
- setIsLoading(false);
44
+ useEffect(() => {
45
+ audioUrl && setAudioUrl(null);
46
+ return () => {
47
+ audioUrl && URL.revokeObjectURL(audioUrl);
58
48
  };
49
+ }, [text]);
59
50
 
60
- // Effect to play audio when audioUrl changes and play state is true
61
- useEffect(() => {
62
- if (!audioUrl || !isPlaying) return;
63
- const audio = new Audio(audioUrl);
64
- audio.playbackRate = speed;
65
- audio.play().then(() => {
66
- audio.onended = () => {
67
- setIsPlaying(false);
68
- isFetchingAudio = false;
69
- };
70
- }).catch(e => {
71
- console.warn("Error playing audio:", e);
72
- setIsPlaying(false);
73
- });
51
+ // Function to generate audio from text using API
52
+ const generateAudio = async () => {
53
+ setIsLoading(true);
74
54
 
75
- return () => {
76
- audio.pause();
55
+ const blob = await ai.getVoice(text, voice || (language ? 'aws_default' : 'openai_alloy'), 1, language);
56
+ setAudioUrl(URL.createObjectURL(blob));
57
+ setIsLoading(false);
58
+ };
59
+
60
+ // Effect to play audio when audioUrl changes and play state is true
61
+ useEffect(() => {
62
+ if (!audioUrl || !isPlaying) return;
63
+ const audio = new Audio(audioUrl);
64
+ audio.playbackRate = speed;
65
+ audio
66
+ .play()
67
+ .then(() => {
68
+ audio.onended = () => {
69
+ setIsPlaying(false);
70
+ isFetchingAudio = false;
77
71
  };
78
- }, [audioUrl, isPlaying, speed]);
72
+ })
73
+ .catch((e) => {
74
+ console.warn('Error playing audio:', e);
75
+ setIsPlaying(false);
76
+ });
79
77
 
80
- const togglePlayback = () => {
81
- if (!isPlaying && !audioUrl) {
82
- generateAudio().then(() => setIsPlaying(true));
83
- } else {
84
- setIsPlaying((prev) => !prev);
85
- }
78
+ return () => {
79
+ audio.pause();
86
80
  };
81
+ }, [audioUrl, isPlaying, speed]);
82
+
83
+ const togglePlayback = () => {
84
+ if (!isPlaying && !audioUrl) {
85
+ generateAudio().then(() => setIsPlaying(true));
86
+ } else {
87
+ setIsPlaying((prev) => !prev);
88
+ }
89
+ };
87
90
 
88
- useEffect(() => {
89
- if (!playOnMount || isFetchingAudio) return;
90
- isFetchingAudio = true;
91
- // console.log("playOnMount", playOnMount);
92
- togglePlayback();
93
- }, [playOnMount]);
91
+ useEffect(() => {
92
+ if (!playOnMount || isFetchingAudio) return;
93
+ isFetchingAudio = true;
94
+ // console.log("playOnMount", playOnMount);
95
+ togglePlayback();
96
+ }, [playOnMount]);
94
97
 
95
- return (
96
- <div className="group relative">
97
- <div className='flex flex-row items-end'>
98
- {!hide && <button className="text-gray-500" onClick={togglePlayback} disabled={isLoading}>
99
- {isLoading ? <Spinner /> : isPlaying ? <FaStopCircle size={"25px"} /> : <FaPlayCircle size={"25px"} />}
100
- </button>}
101
- {enableSpeedAdjustment && (
102
- <div className="ml-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-row text-sm text-gray-500">
103
- <span className='pr-1'>Speed: </span>
104
- <select
105
- value={speed}
106
- 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'
107
- onChange={(e) => setSpeed(parseFloat(e.target.value))}
108
- disabled={isLoading}>
109
- {AudioPlayOptions.map((s) => (
110
- <option key={s} value={s}>{s}</option>
111
- ))}
112
- </select>
113
- </div>
114
- )}
115
- </div>
116
- </div>
117
- );
98
+ return (
99
+ <div className="group relative">
100
+ <div className="flex flex-row items-end">
101
+ {!hide && (
102
+ <button className="text-gray-500" onClick={togglePlayback} disabled={isLoading}>
103
+ {isLoading ? <Spinner /> : isPlaying ? <FaStopCircle size={'25px'} /> : <FaPlayCircle size={'25px'} />}
104
+ </button>
105
+ )}
106
+ {enableSpeedAdjustment && (
107
+ <div className="ml-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-row text-sm text-gray-500">
108
+ <span className="pr-1">Speed: </span>
109
+ <select
110
+ value={speed}
111
+ 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"
112
+ onChange={(e) => setSpeed(parseFloat(e.target.value))}
113
+ disabled={isLoading}
114
+ >
115
+ {AudioPlayOptions.map((s) => (
116
+ <option key={s} value={s}>
117
+ {s}
118
+ </option>
119
+ ))}
120
+ </select>
121
+ </div>
122
+ )}
123
+ </div>
124
+ </div>
125
+ );
118
126
  };
@@ -1,12 +1,12 @@
1
- import React, { useState, useEffect, useRef } from "react";
2
- import { EventBus } from "../../fromRimori/EventBus";
3
- import { RimoriClient } from "../../plugin/RimoriClient";
4
- import { MenuEntry } from "../../fromRimori/PluginTypes";
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { EventBus } from '../../fromRimori/EventBus';
3
+ import { RimoriClient } from '../../plugin/RimoriClient';
4
+ import { MenuEntry } from '../../fromRimori/PluginTypes';
5
5
 
6
6
  export interface Position {
7
- x: number,
8
- y: number,
9
- text?: string
7
+ x: number;
8
+ y: number;
9
+ text?: string;
10
10
  }
11
11
 
12
12
  const ContextMenu = ({ client }: { client: RimoriClient }) => {
@@ -35,7 +35,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
35
35
  const rect = range.getBoundingClientRect();
36
36
 
37
37
  // Center horizontally over the selected text, accounting for menu width
38
- const centerX = rect.left + (rect.width / 2) - (menuWidth / 2);
38
+ const centerX = rect.left + rect.width / 2 - menuWidth / 2;
39
39
 
40
40
  // Position 12px below where the text ends vertically
41
41
  const textEndY = rect.bottom + 12;
@@ -44,12 +44,14 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
44
44
  };
45
45
 
46
46
  useEffect(() => {
47
- const actions = client.plugin.getPluginInfo().installedPlugins.flatMap(p => p.context_menu_actions).filter(Boolean);
47
+ const actions = client.plugin
48
+ .getPluginInfo()
49
+ .installedPlugins.flatMap((p) => p.context_menu_actions)
50
+ .filter(Boolean);
48
51
  setActions(actions);
49
52
  setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
50
53
 
51
-
52
- EventBus.on<{ actions: MenuEntry[] }>("global.contextMenu.createActions", ({ data }) => {
54
+ EventBus.on<{ actions: MenuEntry[] }>('global.contextMenu.createActions', ({ data }) => {
53
55
  setActions([...data.actions, ...actions]);
54
56
  });
55
57
  }, []);
@@ -84,10 +86,9 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
84
86
 
85
87
  // Prevent context menu on textarea or text input selection
86
88
  const target = e.target as HTMLElement;
87
- const isTextInput = target && (
88
- (target.tagName === 'TEXTAREA') ||
89
- (target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'text')
90
- );
89
+ const isTextInput =
90
+ target &&
91
+ (target.tagName === 'TEXTAREA' || (target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'text'));
91
92
  if (isTextInput) {
92
93
  setIsOpen(false);
93
94
  return;
@@ -123,16 +124,16 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
123
124
  }
124
125
  };
125
126
 
126
- document.addEventListener("mouseup", handleMouseUp);
127
- window.addEventListener("mousemove", handleMouseMove);
128
- document.addEventListener("contextmenu", handleMouseUp);
129
- document.addEventListener("selectionchange", handleSelectionChange);
127
+ document.addEventListener('mouseup', handleMouseUp);
128
+ window.addEventListener('mousemove', handleMouseMove);
129
+ document.addEventListener('contextmenu', handleMouseUp);
130
+ document.addEventListener('selectionchange', handleSelectionChange);
130
131
 
131
132
  return () => {
132
- document.removeEventListener("mouseup", handleMouseUp);
133
- window.removeEventListener("mousemove", handleMouseMove);
134
- document.removeEventListener("contextmenu", handleMouseUp);
135
- document.removeEventListener("selectionchange", handleSelectionChange);
133
+ document.removeEventListener('mouseup', handleMouseUp);
134
+ window.removeEventListener('mousemove', handleMouseMove);
135
+ document.removeEventListener('contextmenu', handleMouseUp);
136
+ document.removeEventListener('selectionchange', handleSelectionChange);
136
137
  };
137
138
  }, [openOnTextSelect, isOpen, position.text]);
138
139
 
@@ -144,24 +145,35 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
144
145
  <div
145
146
  ref={menuRef}
146
147
  className="fixed bg-gray-400 dark:bg-gray-700 shadow-lg border border-gray-400 rounded-md overflow-hidden dark:text-white z-50"
147
- style={{ top: position.y, left: position.x }}>
148
+ style={{ top: position.y, left: position.x }}
149
+ >
148
150
  {actions.map((action, index) => (
149
- <MenuEntryItem key={index} icon={action.icon} text={action.text} onClick={() => {
150
- setIsOpen(false);
151
- window.getSelection()?.removeAllRanges();
152
- client.event.emitSidebarAction(action.plugin_id, action.action_key, position.text);
153
- }} />
151
+ <MenuEntryItem
152
+ key={index}
153
+ icon={action.icon}
154
+ text={action.text}
155
+ onClick={() => {
156
+ setIsOpen(false);
157
+ window.getSelection()?.removeAllRanges();
158
+ client.event.emitSidebarAction(action.plugin_id, action.action_key, position.text);
159
+ }}
160
+ />
154
161
  ))}
155
162
  </div>
156
163
  );
157
164
  };
158
165
 
159
- function MenuEntryItem(props: { icon: React.ReactNode, text: string, onClick: () => void }) {
160
- return <button onClick={props.onClick} className="px-4 py-2 text-left hover:bg-gray-500 dark:hover:bg-gray-600 w-full flex flex-row">
161
- <span className="flex-grow">{props.icon}</span>
162
- <span className="flex-grow">{props.text}</span>
163
- {/* <span className="text-sm">Ctrl+Shift+xxxx</span> */}
164
- </button>
166
+ function MenuEntryItem(props: { icon: React.ReactNode; text: string; onClick: () => void }) {
167
+ return (
168
+ <button
169
+ onClick={props.onClick}
170
+ className="px-4 py-2 text-left hover:bg-gray-500 dark:hover:bg-gray-600 w-full flex flex-row"
171
+ >
172
+ <span className="flex-grow">{props.icon}</span>
173
+ <span className="flex-grow">{props.text}</span>
174
+ {/* <span className="text-sm">Ctrl+Shift+xxxx</span> */}
175
+ </button>
176
+ );
165
177
  }
166
178
 
167
179
  export default ContextMenu;
package/src/components.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  // React components and hooks exports
2
- export * from "./components/ai/Assistant";
3
- export * from "./components/ai/Avatar";
4
- export * from "./components/ai/EmbeddedAssistent/VoiceRecoder";
5
- export * from "./components/audio/Playbutton";
6
- export * from "./components/CRUDModal";
7
- export * from "./components/MarkdownEditor";
8
- export * from "./components/Spinner";
9
- export * from "./hooks/UseChatHook";
10
- export * from "./plugin/ThemeSetter";
11
- export * from "./providers/PluginProvider";
2
+ export * from './components/ai/Assistant';
3
+ export * from './components/ai/Avatar';
4
+ export * from './components/ai/EmbeddedAssistent/VoiceRecoder';
5
+ export * from './components/audio/Playbutton';
6
+ export * from './components/CRUDModal';
7
+ export * from './components/MarkdownEditor';
8
+ export * from './components/Spinner';
9
+ export * from './hooks/UseChatHook';
10
+ export * from './plugin/ThemeSetter';
11
+ export * from './providers/PluginProvider';