@rimori/client 1.4.0 → 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.
- package/README.md +77 -71
- package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
- package/dist/cli/scripts/init/dev-registration.js +4 -4
- package/dist/cli/scripts/init/main.js +1 -1
- package/dist/cli/scripts/init/package-setup.d.ts +1 -1
- package/dist/cli/scripts/init/package-setup.js +3 -3
- package/dist/cli/scripts/init/router-transformer.js +19 -12
- package/dist/cli/scripts/init/vite-config.d.ts +2 -2
- package/dist/cli/scripts/init/vite-config.js +2 -2
- package/dist/cli/scripts/release/release-config-upload.js +9 -9
- package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
- package/dist/cli/scripts/release/release-db-update.js +9 -9
- package/dist/cli/scripts/release/release-file-upload.js +1 -1
- package/dist/cli/scripts/release/release.js +2 -2
- package/dist/components/CRUDModal.d.ts +1 -1
- package/dist/components/CRUDModal.js +3 -3
- package/dist/components/MarkdownEditor.js +16 -16
- package/dist/components/Spinner.js +2 -2
- package/dist/components/ai/Assistant.js +7 -8
- package/dist/components/ai/Avatar.d.ts +2 -2
- package/dist/components/ai/Avatar.js +10 -5
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
- package/dist/components/audio/Playbutton.js +10 -7
- package/dist/components/components/ContextMenu.d.ts +1 -1
- package/dist/components/components/ContextMenu.js +19 -16
- package/dist/components.d.ts +10 -10
- package/dist/components.js +10 -10
- package/dist/core/controller/AIController.d.ts +2 -2
- package/dist/core/controller/AIController.js +12 -12
- package/dist/core/controller/ExerciseController.d.ts +2 -2
- package/dist/core/controller/ExerciseController.js +2 -2
- package/dist/core/controller/ObjectController.js +5 -5
- package/dist/core/controller/SettingsController.d.ts +22 -7
- package/dist/core/controller/SettingsController.js +73 -8
- package/dist/core/controller/SharedContentController.d.ts +3 -3
- package/dist/core/controller/SharedContentController.js +38 -20
- package/dist/core/controller/VoiceController.js +6 -4
- package/dist/core/core.d.ts +15 -15
- package/dist/core/core.js +7 -7
- package/dist/fromRimori/EventBus.js +23 -23
- package/dist/fromRimori/PluginTypes.d.ts +4 -4
- package/dist/hooks/UseChatHook.d.ts +3 -3
- package/dist/hooks/UseChatHook.js +9 -3
- package/dist/index.d.ts +10 -10
- package/dist/index.js +9 -9
- package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
- package/dist/plugin/AccomplishmentHandler.js +31 -27
- package/dist/plugin/AudioController.d.ts +1 -1
- package/dist/plugin/AudioController.js +6 -6
- package/dist/plugin/Logger.js +15 -13
- package/dist/plugin/PluginController.d.ts +7 -1
- package/dist/plugin/PluginController.js +32 -27
- package/dist/plugin/RimoriClient.d.ts +17 -18
- package/dist/plugin/RimoriClient.js +31 -31
- package/dist/plugin/StandaloneClient.d.ts +1 -1
- package/dist/plugin/StandaloneClient.js +35 -16
- package/dist/plugin/ThemeSetter.js +4 -4
- package/dist/providers/PluginProvider.js +44 -14
- package/dist/utils/Language.js +57 -57
- package/dist/utils/PluginUtils.js +3 -3
- package/dist/utils/difficultyConverter.d.ts +1 -1
- package/dist/utils/difficultyConverter.js +1 -1
- package/dist/utils/endpoint.js +2 -2
- package/dist/worker/WorkerSetup.d.ts +1 -1
- package/dist/worker/WorkerSetup.js +6 -6
- package/example/docs/devdocs.md +50 -40
- package/example/docs/overview.md +1 -1
- package/example/docs/userdocs.md +4 -1
- package/example/rimori.config.ts +51 -49
- package/example/worker/vite.config.ts +3 -3
- package/example/worker/worker.ts +2 -2
- package/package.json +14 -8
- package/prettier.config.js +1 -1
- package/src/cli/scripts/init/dev-registration.ts +5 -8
- package/src/cli/scripts/init/env-setup.ts +1 -1
- package/src/cli/scripts/init/file-operations.ts +1 -1
- package/src/cli/scripts/init/html-cleaner.ts +2 -5
- package/src/cli/scripts/init/main.ts +16 -13
- package/src/cli/scripts/init/package-setup.ts +11 -15
- package/src/cli/scripts/init/router-transformer.ts +40 -37
- package/src/cli/scripts/init/tailwind-config.ts +17 -26
- package/src/cli/scripts/init/vite-config.ts +3 -3
- package/src/cli/scripts/release/release-config-upload.ts +11 -11
- package/src/cli/scripts/release/release-db-update.ts +12 -12
- package/src/cli/scripts/release/release-file-upload.ts +2 -2
- package/src/cli/scripts/release/release.ts +4 -4
- package/src/cli/types/DatabaseTypes.ts +2 -10
- package/src/components/CRUDModal.tsx +64 -48
- package/src/components/MarkdownEditor.tsx +58 -27
- package/src/components/Spinner.tsx +24 -17
- package/src/components/ai/Assistant.tsx +70 -70
- package/src/components/ai/Avatar.tsx +17 -14
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +3 -4
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
- package/src/components/ai/utils.ts +4 -4
- package/src/components/audio/Playbutton.tsx +101 -93
- package/src/components/components/ContextMenu.tsx +47 -35
- package/src/components.ts +10 -10
- package/src/core/controller/AIController.ts +29 -19
- package/src/core/controller/ExerciseController.ts +16 -23
- package/src/core/controller/ObjectController.ts +15 -10
- package/src/core/controller/SettingsController.ts +89 -16
- package/src/core/controller/SharedContentController.ts +80 -44
- package/src/core/controller/VoiceController.ts +10 -8
- package/src/core/core.ts +15 -16
- package/src/fromRimori/EventBus.ts +76 -47
- package/src/fromRimori/PluginTypes.ts +26 -17
- package/src/fromRimori/readme.md +2 -2
- package/src/hooks/UseChatHook.ts +25 -15
- package/src/index.ts +10 -10
- package/src/plugin/AccomplishmentHandler.ts +53 -35
- package/src/plugin/AudioController.ts +18 -12
- package/src/plugin/Logger.ts +28 -21
- package/src/plugin/PluginController.ts +60 -44
- package/src/plugin/RimoriClient.ts +102 -72
- package/src/plugin/StandaloneClient.ts +51 -24
- package/src/plugin/ThemeSetter.ts +5 -5
- package/src/providers/PluginProvider.tsx +90 -36
- package/src/style.scss +3 -3
- package/src/utils/Language.ts +58 -58
- package/src/utils/PluginUtils.ts +16 -20
- package/src/utils/difficultyConverter.ts +2 -2
- package/src/utils/endpoint.ts +3 -2
- package/src/worker/WorkerSetup.ts +8 -9
- package/tsconfig.json +2 -4
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { FaPlayCircle, FaStopCircle } from
|
|
3
|
-
import { useRimori } from
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
text,
|
|
25
|
+
voice,
|
|
26
|
+
language,
|
|
27
|
+
hide,
|
|
28
|
+
playListenerEvent,
|
|
29
|
+
initialSpeed = 1.0,
|
|
30
|
+
playOnMount = false,
|
|
31
|
+
enableSpeedAdjustment = false,
|
|
32
32
|
}) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!playListenerEvent) return;
|
|
41
|
+
EventBus.on(playListenerEvent, () => togglePlayback());
|
|
42
|
+
}, [playListenerEvent]);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
72
|
+
})
|
|
73
|
+
.catch((e) => {
|
|
74
|
+
console.warn('Error playing audio:', e);
|
|
75
|
+
setIsPlaying(false);
|
|
76
|
+
});
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!playOnMount || isFetchingAudio) return;
|
|
93
|
+
isFetchingAudio = true;
|
|
94
|
+
// console.log("playOnMount", playOnMount);
|
|
95
|
+
togglePlayback();
|
|
96
|
+
}, [playOnMount]);
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
2
|
-
import { EventBus } from
|
|
3
|
-
import { RimoriClient } from
|
|
4
|
-
import { MenuEntry } from
|
|
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 +
|
|
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
|
|
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 =
|
|
88
|
-
|
|
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(
|
|
127
|
-
window.addEventListener(
|
|
128
|
-
document.addEventListener(
|
|
129
|
-
document.addEventListener(
|
|
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(
|
|
133
|
-
window.removeEventListener(
|
|
134
|
-
document.removeEventListener(
|
|
135
|
-
document.removeEventListener(
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
160
|
-
return
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export * from
|
|
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';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tool } from
|
|
1
|
+
import { Tool } from '../../fromRimori/PluginTypes';
|
|
2
2
|
|
|
3
3
|
export interface ToolInvocation {
|
|
4
4
|
toolCallId: string;
|
|
@@ -8,7 +8,7 @@ export interface ToolInvocation {
|
|
|
8
8
|
|
|
9
9
|
export interface Message {
|
|
10
10
|
id?: string;
|
|
11
|
-
role:
|
|
11
|
+
role: 'user' | 'assistant' | 'system';
|
|
12
12
|
content: string;
|
|
13
13
|
toolCalls?: ToolInvocation[];
|
|
14
14
|
}
|
|
@@ -17,15 +17,26 @@ export async function generateText(backendUrl: string, messages: Message[], tool
|
|
|
17
17
|
const response = await fetch(`${backendUrl}/ai/llm`, {
|
|
18
18
|
method: 'POST',
|
|
19
19
|
body: JSON.stringify({ messages, tools }),
|
|
20
|
-
headers: {
|
|
20
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
return await response.json();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export type OnLLMResponse = (
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
export type OnLLMResponse = (
|
|
27
|
+
id: string,
|
|
28
|
+
response: string,
|
|
29
|
+
finished: boolean,
|
|
30
|
+
toolInvocations?: ToolInvocation[],
|
|
31
|
+
) => void;
|
|
32
|
+
|
|
33
|
+
export async function streamChatGPT(
|
|
34
|
+
backendUrl: string,
|
|
35
|
+
messages: Message[],
|
|
36
|
+
tools: Tool[],
|
|
37
|
+
onResponse: OnLLMResponse,
|
|
38
|
+
token: string,
|
|
39
|
+
) {
|
|
29
40
|
const messageId = Math.random().toString(36).substring(3);
|
|
30
41
|
let currentMessages: Message[] = [...messages];
|
|
31
42
|
|
|
@@ -33,7 +44,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
33
44
|
messageId,
|
|
34
45
|
messageCount: messages.length,
|
|
35
46
|
toolCount: tools.length,
|
|
36
|
-
backendUrl
|
|
47
|
+
backendUrl,
|
|
37
48
|
});
|
|
38
49
|
|
|
39
50
|
while (true) {
|
|
@@ -43,7 +54,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
43
54
|
const response = await fetch(`${backendUrl}/ai/llm`, {
|
|
44
55
|
method: 'POST',
|
|
45
56
|
body: JSON.stringify({ messages: messagesForApi, tools, stream: true }),
|
|
46
|
-
headers: {
|
|
57
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
47
58
|
});
|
|
48
59
|
|
|
49
60
|
if (!response.ok) {
|
|
@@ -58,12 +69,12 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
58
69
|
const reader = response.body.getReader();
|
|
59
70
|
const decoder = new TextDecoder('utf-8');
|
|
60
71
|
|
|
61
|
-
let content =
|
|
72
|
+
let content = '';
|
|
62
73
|
let done = false;
|
|
63
|
-
let toolInvocations: { toolCallId: string
|
|
64
|
-
let currentTextId =
|
|
74
|
+
let toolInvocations: { toolCallId: string; toolName: string; args: any }[] = [];
|
|
75
|
+
let currentTextId = '';
|
|
65
76
|
let isToolCallMode = false;
|
|
66
|
-
let buffer =
|
|
77
|
+
let buffer = ''; // Buffer for incomplete chunks
|
|
67
78
|
|
|
68
79
|
while (!done) {
|
|
69
80
|
const { value, done: readerDone } = await reader.read();
|
|
@@ -77,7 +88,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
77
88
|
|
|
78
89
|
// Keep the last line in buffer if it's incomplete
|
|
79
90
|
if (lines.length > 1) {
|
|
80
|
-
buffer = lines.pop() ||
|
|
91
|
+
buffer = lines.pop() || '';
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
for (const line of lines) {
|
|
@@ -162,7 +173,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
162
173
|
toolInvocations.push({
|
|
163
174
|
toolCallId: data.toolCallId,
|
|
164
175
|
toolName: data.toolName,
|
|
165
|
-
args: data.args || data.input
|
|
176
|
+
args: data.args || data.input,
|
|
166
177
|
});
|
|
167
178
|
}
|
|
168
179
|
break;
|
|
@@ -225,7 +236,7 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
225
236
|
if (content || toolInvocations.length > 0) {
|
|
226
237
|
currentMessages.push({
|
|
227
238
|
id: messageId,
|
|
228
|
-
role:
|
|
239
|
+
role: 'assistant',
|
|
229
240
|
content: content,
|
|
230
241
|
toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
|
|
231
242
|
});
|
|
@@ -237,20 +248,20 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
237
248
|
|
|
238
249
|
const toolResults: Message[] = [];
|
|
239
250
|
for (const toolInvocation of toolInvocations) {
|
|
240
|
-
const tool = tools.find(t => t.name === toolInvocation.toolName);
|
|
251
|
+
const tool = tools.find((t) => t.name === toolInvocation.toolName);
|
|
241
252
|
if (tool && tool.execute) {
|
|
242
253
|
try {
|
|
243
254
|
const result = await tool.execute(toolInvocation.args);
|
|
244
255
|
toolResults.push({
|
|
245
256
|
id: Math.random().toString(36).substring(3),
|
|
246
|
-
role:
|
|
257
|
+
role: 'user',
|
|
247
258
|
content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
|
|
248
259
|
});
|
|
249
260
|
} catch (error) {
|
|
250
261
|
console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
|
|
251
262
|
toolResults.push({
|
|
252
263
|
id: Math.random().toString(36).substring(3),
|
|
253
|
-
role:
|
|
264
|
+
role: 'user',
|
|
254
265
|
content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
|
|
255
266
|
});
|
|
256
267
|
}
|
|
@@ -275,7 +286,6 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
275
286
|
|
|
276
287
|
onResponse(messageId, content, true, toolInvocations);
|
|
277
288
|
return;
|
|
278
|
-
|
|
279
289
|
} catch (error) {
|
|
280
290
|
console.error('Error in streamChatGPT:', error);
|
|
281
291
|
onResponse(messageId, `Error: ${error instanceof Error ? error.message : String(error)}`, true, []);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SupabaseClient } from
|
|
2
|
-
import { PluginController } from
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { PluginController } from '../../plugin/PluginController';
|
|
3
3
|
|
|
4
4
|
export type TriggerAction = { action_key: string } & Record<string, string | number | boolean>;
|
|
5
5
|
|
|
@@ -57,17 +57,14 @@ export class ExerciseController {
|
|
|
57
57
|
*/
|
|
58
58
|
public async addExercise(params: CreateExerciseParams): Promise<Exercise> {
|
|
59
59
|
const token = await this.pluginController.getToken();
|
|
60
|
-
const response = await fetch(
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
body: JSON.stringify(params),
|
|
69
|
-
}
|
|
70
|
-
);
|
|
60
|
+
const response = await fetch(`${this.pluginController.getBackendUrl()}/exercises`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
Authorization: `Bearer ${token}`,
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(params),
|
|
67
|
+
});
|
|
71
68
|
|
|
72
69
|
if (!response.ok) {
|
|
73
70
|
const errorText = await response.text();
|
|
@@ -84,15 +81,12 @@ export class ExerciseController {
|
|
|
84
81
|
*/
|
|
85
82
|
public async deleteExercise(id: string): Promise<{ success: boolean; message: string }> {
|
|
86
83
|
const token = await this.pluginController.getToken();
|
|
87
|
-
const response = await fetch(
|
|
88
|
-
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
);
|
|
84
|
+
const response = await fetch(`${this.pluginController.getBackendUrl()}/exercises/${id}`, {
|
|
85
|
+
method: 'DELETE',
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${token}`,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
96
90
|
|
|
97
91
|
if (!response.ok) {
|
|
98
92
|
const errorText = await response.text();
|
|
@@ -102,4 +96,3 @@ export class ExerciseController {
|
|
|
102
96
|
return await response.json();
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
|
-
|
|
@@ -3,8 +3,8 @@ type PrimitiveType = 'string' | 'number' | 'boolean';
|
|
|
3
3
|
// This is the type that can appear in the `type` property
|
|
4
4
|
type ObjectToolParameterType =
|
|
5
5
|
| PrimitiveType
|
|
6
|
-
| { [key: string]: ObjectToolParameter }
|
|
7
|
-
| [{ [key: string]: ObjectToolParameter }];
|
|
6
|
+
| { [key: string]: ObjectToolParameter } // for nested objects
|
|
7
|
+
| [{ [key: string]: ObjectToolParameter }]; // for arrays of objects (notice the tuple type)
|
|
8
8
|
|
|
9
9
|
interface ObjectToolParameter {
|
|
10
10
|
type: ObjectToolParameterType;
|
|
@@ -15,10 +15,10 @@ interface ObjectToolParameter {
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The tools that the AI can use.
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* The key is the name of the tool.
|
|
20
20
|
* The value is the parameter of the tool.
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
22
|
*/
|
|
23
23
|
export type ObjectTool = {
|
|
24
24
|
[key: string]: ObjectToolParameter;
|
|
@@ -50,14 +50,19 @@ export async function generateObject(backendUrl: string, request: ObjectRequest,
|
|
|
50
50
|
behaviour: request.behaviour,
|
|
51
51
|
instructions: request.instructions,
|
|
52
52
|
}),
|
|
53
|
-
headers: {
|
|
54
|
-
}).then(response => response.json());
|
|
53
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
54
|
+
}).then((response) => response.json());
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// TODO adjust stream to work with object
|
|
58
58
|
export type OnLLMResponse = (id: string, response: string, finished: boolean, toolInvocations?: any[]) => void;
|
|
59
59
|
|
|
60
|
-
export async function streamObject(
|
|
60
|
+
export async function streamObject(
|
|
61
|
+
backendUrl: string,
|
|
62
|
+
request: ObjectRequest,
|
|
63
|
+
onResponse: OnLLMResponse,
|
|
64
|
+
token: string,
|
|
65
|
+
) {
|
|
61
66
|
const messageId = Math.random().toString(36).substring(3);
|
|
62
67
|
const response = await fetch(`${backendUrl}/ai/llm-object`, {
|
|
63
68
|
method: 'POST',
|
|
@@ -67,7 +72,7 @@ export async function streamObject(backendUrl: string, request: ObjectRequest, o
|
|
|
67
72
|
systemInstructions: request.behaviour,
|
|
68
73
|
secondaryInstructions: request.instructions,
|
|
69
74
|
}),
|
|
70
|
-
headers: {
|
|
75
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
71
76
|
});
|
|
72
77
|
|
|
73
78
|
if (!response.body) {
|
|
@@ -78,7 +83,7 @@ export async function streamObject(backendUrl: string, request: ObjectRequest, o
|
|
|
78
83
|
const reader = response.body.getReader();
|
|
79
84
|
const decoder = new TextDecoder('utf-8');
|
|
80
85
|
|
|
81
|
-
let content =
|
|
86
|
+
let content = '';
|
|
82
87
|
let done = false;
|
|
83
88
|
let toolInvocations: any[] = [];
|
|
84
89
|
while (!done) {
|
|
@@ -86,7 +91,7 @@ export async function streamObject(backendUrl: string, request: ObjectRequest, o
|
|
|
86
91
|
|
|
87
92
|
if (value) {
|
|
88
93
|
const chunk = decoder.decode(value, { stream: true });
|
|
89
|
-
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
|
94
|
+
const lines = chunk.split('\n').filter((line) => line.trim() !== '');
|
|
90
95
|
|
|
91
96
|
for (const line of lines) {
|
|
92
97
|
const data = line.substring(3, line.length - 1);
|