@rimori/client 1.1.10 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -63
- package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
- package/dist/cli/scripts/init/dev-registration.js +174 -0
- package/dist/cli/scripts/init/env-setup.d.ts +9 -0
- package/dist/cli/scripts/init/env-setup.js +43 -0
- package/dist/cli/scripts/init/file-operations.d.ts +4 -0
- package/dist/cli/scripts/init/file-operations.js +51 -0
- package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
- package/dist/cli/scripts/init/html-cleaner.js +38 -0
- package/dist/cli/scripts/init/main.d.ts +2 -0
- package/dist/cli/scripts/init/main.js +160 -0
- package/dist/cli/scripts/init/package-setup.d.ts +32 -0
- package/dist/cli/scripts/init/package-setup.js +75 -0
- package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
- package/dist/cli/scripts/init/router-transformer.js +254 -0
- package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
- package/dist/cli/scripts/init/tailwind-config.js +56 -0
- package/dist/cli/scripts/init/vite-config.d.ts +20 -0
- package/dist/cli/scripts/init/vite-config.js +54 -0
- package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
- package/dist/cli/scripts/release/release-config-upload.js +116 -0
- package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
- package/dist/cli/scripts/release/release-db-update.js +100 -0
- package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
- package/dist/cli/scripts/release/release-file-upload.js +136 -0
- package/dist/cli/scripts/release/release.d.ts +23 -0
- package/dist/cli/scripts/release/release.js +70 -0
- package/dist/cli/types/DatabaseTypes.d.ts +103 -0
- package/dist/cli/types/DatabaseTypes.js +2 -0
- package/dist/components/LoggerExample.d.ts +6 -0
- package/dist/components/LoggerExample.js +79 -0
- package/dist/components/ai/Assistant.js +5 -5
- package/dist/components/ai/Avatar.d.ts +3 -2
- package/dist/components/ai/Avatar.js +11 -6
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +48 -33
- package/dist/components/ai/utils.js +0 -1
- package/dist/components/audio/Playbutton.js +4 -4
- package/dist/{core → components}/components/ContextMenu.js +50 -11
- package/dist/components.d.ts +5 -5
- package/dist/components.js +5 -5
- package/dist/core/controller/AIController.d.ts +15 -0
- package/dist/core/controller/AIController.js +253 -0
- package/dist/core/controller/AudioController.d.ts +0 -0
- package/dist/core/controller/AudioController.js +1 -0
- package/dist/{controller → core/controller}/ObjectController.d.ts +10 -2
- package/dist/{controller → core/controller}/ObjectController.js +8 -8
- package/dist/{controller → core/controller}/SettingsController.d.ts +28 -4
- package/dist/{controller → core/controller}/SettingsController.js +0 -25
- package/dist/{controller → core/controller}/SharedContentController.d.ts +31 -3
- package/dist/{controller → core/controller}/SharedContentController.js +77 -26
- package/dist/core/controller/VoiceController.d.ts +9 -0
- package/dist/{controller → core/controller}/VoiceController.js +11 -4
- package/dist/core/core.d.ts +14 -0
- package/dist/core/core.js +8 -0
- package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
- package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +26 -9
- package/dist/fromRimori/PluginTypes.d.ts +174 -0
- package/dist/hooks/UseChatHook.d.ts +2 -1
- package/dist/hooks/UseChatHook.js +6 -4
- package/dist/hooks/UseLogger.d.ts +30 -0
- package/dist/hooks/UseLogger.js +122 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
- package/dist/plugin/AccomplishmentHandler.js +1 -1
- package/dist/plugin/AudioController.d.ts +37 -0
- package/dist/plugin/AudioController.js +68 -0
- package/dist/plugin/Logger.d.ts +68 -0
- package/dist/plugin/Logger.js +256 -0
- package/dist/plugin/LoggerExample.d.ts +16 -0
- package/dist/plugin/LoggerExample.js +140 -0
- package/dist/plugin/PluginController.d.ts +30 -5
- package/dist/plugin/PluginController.js +182 -53
- package/dist/plugin/RimoriClient.d.ts +68 -21
- package/dist/plugin/RimoriClient.js +88 -41
- package/dist/plugin/StandaloneClient.d.ts +1 -0
- package/dist/plugin/StandaloneClient.js +24 -10
- package/dist/plugin/ThemeSetter.d.ts +2 -1
- package/dist/plugin/ThemeSetter.js +13 -7
- package/dist/providers/PluginProvider.d.ts +4 -1
- package/dist/providers/PluginProvider.js +39 -13
- package/dist/utils/Language.d.ts +2 -1
- package/dist/utils/Language.js +4 -2
- package/dist/utils/audioFormats.d.ts +26 -0
- package/dist/utils/audioFormats.js +67 -0
- package/dist/utils/difficultyConverter.js +1 -1
- package/dist/utils/endpoint.d.ts +2 -0
- package/dist/utils/endpoint.js +2 -0
- package/dist/worker/WorkerSetup.d.ts +3 -2
- package/dist/worker/WorkerSetup.js +22 -65
- package/example/docs/devdocs.md +231 -0
- package/example/docs/overview.md +29 -0
- package/example/docs/userdocs.md +123 -0
- package/example/rimori.config.ts +89 -0
- package/example/worker/vite.config.ts +23 -0
- package/example/worker/worker.ts +11 -0
- package/package.json +16 -9
- package/src/cli/scripts/init/dev-registration.ts +192 -0
- package/src/cli/scripts/init/env-setup.ts +44 -0
- package/src/cli/scripts/init/file-operations.ts +58 -0
- package/src/cli/scripts/init/html-cleaner.ts +48 -0
- package/src/cli/scripts/init/main.ts +172 -0
- package/src/cli/scripts/init/package-setup.ts +117 -0
- package/src/cli/scripts/init/router-transformer.ts +329 -0
- package/src/cli/scripts/init/tailwind-config.ts +75 -0
- package/src/cli/scripts/init/vite-config.ts +73 -0
- package/src/cli/scripts/release/release-config-upload.ts +114 -0
- package/src/cli/scripts/release/release-db-update.ts +97 -0
- package/src/cli/scripts/release/release-file-upload.ts +138 -0
- package/src/cli/scripts/release/release.ts +69 -0
- package/src/cli/types/DatabaseTypes.ts +117 -0
- package/src/components/ai/Assistant.tsx +5 -5
- package/src/components/ai/Avatar.tsx +25 -8
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +50 -35
- package/src/components/ai/utils.ts +0 -2
- package/src/components/audio/Playbutton.tsx +4 -4
- package/src/{core → components}/components/ContextMenu.tsx +56 -12
- package/src/components.ts +6 -6
- package/src/core/controller/AIController.ts +283 -0
- package/src/core/controller/ObjectController.ts +115 -0
- package/src/{controller → core/controller}/SettingsController.ts +29 -29
- package/src/{controller → core/controller}/SharedContentController.ts +91 -29
- package/src/core/controller/VoiceController.ts +31 -0
- package/src/core/core.ts +16 -0
- package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +29 -11
- package/src/fromRimori/PluginTypes.ts +205 -0
- package/src/hooks/UseChatHook.ts +8 -5
- package/src/index.ts +6 -3
- package/src/plugin/AccomplishmentHandler.ts +1 -1
- package/src/plugin/AudioController.ts +58 -0
- package/src/plugin/Logger.ts +324 -0
- package/src/plugin/PluginController.ts +203 -63
- package/src/plugin/RimoriClient.ts +127 -55
- package/src/plugin/StandaloneClient.ts +30 -11
- package/src/plugin/ThemeSetter.ts +16 -9
- package/src/providers/PluginProvider.tsx +46 -13
- package/src/utils/Language.ts +4 -2
- package/src/utils/difficultyConverter.ts +3 -3
- package/src/utils/endpoint.ts +2 -0
- package/src/worker/WorkerSetup.ts +13 -60
- package/dist/components/PluginController.d.ts +0 -21
- package/dist/components/PluginController.js +0 -116
- package/dist/controller/AIController.d.ts +0 -23
- package/dist/controller/AIController.js +0 -93
- package/dist/controller/SidePluginController.d.ts +0 -3
- package/dist/controller/SidePluginController.js +0 -31
- package/dist/controller/VoiceController.d.ts +0 -10
- package/dist/core.d.ts +0 -7
- package/dist/core.js +0 -7
- package/dist/plugin/ContextMenu.d.ts +0 -17
- package/dist/plugin/ContextMenu.js +0 -45
- package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
- package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
- package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
- package/dist/providers/PluginController.d.ts +0 -21
- package/dist/providers/PluginController.js +0 -116
- package/dist/types/Actions.d.ts +0 -4
- package/dist/types/Actions.js +0 -1
- package/src/controller/AIController.ts +0 -112
- package/src/controller/ObjectController.ts +0 -107
- package/src/controller/SidePluginController.ts +0 -25
- package/src/controller/VoiceController.ts +0 -26
- package/src/core.ts +0 -8
- package/src/plugin/fromRimori/PluginTypes.ts +0 -64
- package/src/types/Actions.ts +0 -6
- /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
- /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
- /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
-
import { EventBus } from "../../
|
|
2
|
+
import { EventBus } from "../../fromRimori/EventBus";
|
|
3
3
|
import { RimoriClient } from "../../plugin/RimoriClient";
|
|
4
|
-
import { MenuEntry } from "../../
|
|
4
|
+
import { MenuEntry } from "../../fromRimori/PluginTypes";
|
|
5
5
|
|
|
6
6
|
export interface Position {
|
|
7
7
|
x: number,
|
|
@@ -14,28 +14,64 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
|
|
|
14
14
|
const [actions, setActions] = useState<MenuEntry[]>([]);
|
|
15
15
|
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
|
|
16
16
|
const [openOnTextSelect, setOpenOnTextSelect] = useState(false);
|
|
17
|
+
const [menuWidth, setMenuWidth] = useState<number>(0);
|
|
17
18
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const isMobile = window.innerWidth < 768;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculates position for mobile context menu based on selected text bounds.
|
|
23
|
+
* Centers the menu horizontally over the selected text and positions it 30px below the text's end.
|
|
24
|
+
* @param selectedText - The currently selected text
|
|
25
|
+
* @param menuWidth - The width of the menu to center properly
|
|
26
|
+
* @returns Position object with x and y coordinates
|
|
27
|
+
*/
|
|
28
|
+
const calculateMobilePosition = (selectedText: string, menuWidth: number = 0): Position => {
|
|
29
|
+
const selection = window.getSelection();
|
|
30
|
+
if (!selection || !selectedText) {
|
|
31
|
+
return { x: 0, y: 0, text: selectedText };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const range = selection.getRangeAt(0);
|
|
35
|
+
const rect = range.getBoundingClientRect();
|
|
36
|
+
|
|
37
|
+
// Center horizontally over the selected text, accounting for menu width
|
|
38
|
+
const centerX = rect.left + (rect.width / 2) - (menuWidth / 2);
|
|
39
|
+
|
|
40
|
+
// Position 12px below where the text ends vertically
|
|
41
|
+
const textEndY = rect.bottom + 12;
|
|
42
|
+
|
|
43
|
+
return { x: centerX, y: textEndY, text: selectedText };
|
|
44
|
+
};
|
|
18
45
|
|
|
19
46
|
useEffect(() => {
|
|
20
|
-
client.plugin.
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
const actions = client.plugin.getPluginInfo().installedPlugins.flatMap(p => p.context_menu_actions).filter(Boolean);
|
|
48
|
+
setActions(actions);
|
|
49
|
+
setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
|
|
23
50
|
|
|
24
|
-
client.plugin.getUserInfo().then((userInfo) => {
|
|
25
|
-
setOpenOnTextSelect(userInfo.context_menu_on_select);
|
|
26
|
-
})
|
|
27
51
|
|
|
28
52
|
EventBus.on<{ actions: MenuEntry[] }>("global.contextMenu.createActions", ({ data }) => {
|
|
29
53
|
setActions([...data.actions, ...actions]);
|
|
30
54
|
});
|
|
31
55
|
}, []);
|
|
32
56
|
|
|
57
|
+
// Update menu width when menu is rendered
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (isOpen && menuRef.current) {
|
|
60
|
+
setMenuWidth(menuRef.current.offsetWidth);
|
|
61
|
+
}
|
|
62
|
+
}, [isOpen, actions]);
|
|
63
|
+
|
|
33
64
|
useEffect(() => {
|
|
34
65
|
// Track mouse position globally
|
|
35
66
|
const handleMouseMove = (e: MouseEvent) => {
|
|
36
67
|
const selectedText = window.getSelection()?.toString().trim();
|
|
37
68
|
if (isOpen && selectedText === position.text) return;
|
|
38
|
-
|
|
69
|
+
|
|
70
|
+
if (isMobile && selectedText) {
|
|
71
|
+
setPosition(calculateMobilePosition(selectedText, menuWidth));
|
|
72
|
+
} else {
|
|
73
|
+
setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
|
|
74
|
+
}
|
|
39
75
|
};
|
|
40
76
|
|
|
41
77
|
const handleMouseUp = (e: MouseEvent) => {
|
|
@@ -64,18 +100,26 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
|
|
|
64
100
|
if (e.button === 2) {
|
|
65
101
|
e.preventDefault();
|
|
66
102
|
}
|
|
67
|
-
|
|
103
|
+
|
|
104
|
+
if (isMobile) {
|
|
105
|
+
setPosition(calculateMobilePosition(selectedText, menuWidth));
|
|
106
|
+
} else {
|
|
107
|
+
setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
|
|
108
|
+
}
|
|
68
109
|
setIsOpen(true);
|
|
69
110
|
} else {
|
|
70
111
|
setIsOpen(false);
|
|
71
112
|
}
|
|
72
113
|
};
|
|
73
114
|
|
|
74
|
-
// Add selectionchange listener to close menu if selection is cleared
|
|
115
|
+
// Add selectionchange listener to close menu if selection is cleared and update position for mobile
|
|
75
116
|
const handleSelectionChange = () => {
|
|
76
117
|
const selectedText = window.getSelection()?.toString().trim();
|
|
77
118
|
if (!selectedText && isOpen) {
|
|
78
119
|
setIsOpen(false);
|
|
120
|
+
} else if (selectedText && isOpen && isMobile) {
|
|
121
|
+
// Update position in real-time as text selection changes on mobile
|
|
122
|
+
setPosition(calculateMobilePosition(selectedText, menuWidth));
|
|
79
123
|
}
|
|
80
124
|
};
|
|
81
125
|
|
|
@@ -105,7 +149,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
|
|
|
105
149
|
<MenuEntryItem key={index} icon={action.icon} text={action.text} onClick={() => {
|
|
106
150
|
setIsOpen(false);
|
|
107
151
|
window.getSelection()?.removeAllRanges();
|
|
108
|
-
client.event.emitSidebarAction(action.
|
|
152
|
+
client.event.emitSidebarAction(action.plugin_id, action.action_key, position.text);
|
|
109
153
|
}} />
|
|
110
154
|
))}
|
|
111
155
|
</div>
|
package/src/components.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// React components and hooks exports
|
|
2
|
-
export * from "./components/
|
|
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";
|
|
3
6
|
export * from "./components/CRUDModal";
|
|
7
|
+
export * from "./components/MarkdownEditor";
|
|
4
8
|
export * from "./components/Spinner";
|
|
5
|
-
export * from "./components/audio/Playbutton";
|
|
6
9
|
export * from "./hooks/UseChatHook";
|
|
7
10
|
export * from "./plugin/ThemeSetter";
|
|
8
|
-
export * from "./providers/PluginProvider";
|
|
9
|
-
export * from "./components/ai/Avatar";
|
|
10
|
-
export * from "./components/ai/Assistant";
|
|
11
|
-
export * from "./types/Actions";
|
|
11
|
+
export * from "./providers/PluginProvider";
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Tool } from "../../fromRimori/PluginTypes";
|
|
2
|
+
|
|
3
|
+
export interface ToolInvocation {
|
|
4
|
+
toolCallId: string;
|
|
5
|
+
toolName: string;
|
|
6
|
+
args: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Message {
|
|
10
|
+
id?: string;
|
|
11
|
+
role: "user" | "assistant" | "system"
|
|
12
|
+
content: string;
|
|
13
|
+
toolCalls?: ToolInvocation[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function generateText(backendUrl: string, messages: Message[], tools: Tool[], token: string) {
|
|
17
|
+
const response = await fetch(`${backendUrl}/ai/llm`, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: JSON.stringify({ messages, tools }),
|
|
20
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return await response.json();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type OnLLMResponse = (id: string, response: string, finished: boolean, toolInvocations?: ToolInvocation[]) => void;
|
|
27
|
+
|
|
28
|
+
export async function streamChatGPT(backendUrl: string, messages: Message[], tools: Tool[], onResponse: OnLLMResponse, token: string) {
|
|
29
|
+
const messageId = Math.random().toString(36).substring(3);
|
|
30
|
+
let currentMessages: Message[] = [...messages];
|
|
31
|
+
|
|
32
|
+
console.log('Starting streamChatGPT with:', {
|
|
33
|
+
messageId,
|
|
34
|
+
messageCount: messages.length,
|
|
35
|
+
toolCount: tools.length,
|
|
36
|
+
backendUrl
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
const messagesForApi = currentMessages.map(({ id, ...rest }) => rest);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(`${backendUrl}/ai/llm`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: JSON.stringify({ messages: messagesForApi, tools, stream: true }),
|
|
46
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!response.body) {
|
|
54
|
+
console.error('No response body.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const reader = response.body.getReader();
|
|
59
|
+
const decoder = new TextDecoder('utf-8');
|
|
60
|
+
|
|
61
|
+
let content = "";
|
|
62
|
+
let done = false;
|
|
63
|
+
let toolInvocations: { toolCallId: string, toolName: string, args: any }[] = [];
|
|
64
|
+
let currentTextId = "";
|
|
65
|
+
let isToolCallMode = false;
|
|
66
|
+
let buffer = ""; // Buffer for incomplete chunks
|
|
67
|
+
|
|
68
|
+
while (!done) {
|
|
69
|
+
const { value, done: readerDone } = await reader.read();
|
|
70
|
+
|
|
71
|
+
if (value) {
|
|
72
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
73
|
+
buffer += chunk;
|
|
74
|
+
|
|
75
|
+
// Split by lines, but handle incomplete lines
|
|
76
|
+
const lines = buffer.split('\n');
|
|
77
|
+
|
|
78
|
+
// Keep the last line in buffer if it's incomplete
|
|
79
|
+
if (lines.length > 1) {
|
|
80
|
+
buffer = lines.pop() || "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
if (line.trim() === '') continue;
|
|
85
|
+
|
|
86
|
+
// Handle the new streaming format
|
|
87
|
+
if (line.startsWith('data: ')) {
|
|
88
|
+
const dataStr = line.substring(6); // Remove 'data: ' prefix
|
|
89
|
+
|
|
90
|
+
// Handle [DONE] marker
|
|
91
|
+
if (dataStr === '[DONE]') {
|
|
92
|
+
done = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const data = JSON.parse(dataStr);
|
|
98
|
+
|
|
99
|
+
// Log the first message to understand the format
|
|
100
|
+
if (!content && !isToolCallMode) {
|
|
101
|
+
console.log('First stream message received:', data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
switch (data.type) {
|
|
105
|
+
case 'start':
|
|
106
|
+
// Stream started, no action needed
|
|
107
|
+
console.log('Stream started');
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'start-step':
|
|
111
|
+
// Step started, no action needed
|
|
112
|
+
console.log('Step started');
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case 'reasoning-start':
|
|
116
|
+
// Reasoning started, no action needed
|
|
117
|
+
console.log('Reasoning started:', data.id);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'reasoning-end':
|
|
121
|
+
// Reasoning ended, no action needed
|
|
122
|
+
console.log('Reasoning ended:', data.id);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'text-start':
|
|
126
|
+
// Text generation started, store the ID
|
|
127
|
+
currentTextId = data.id;
|
|
128
|
+
console.log('Text generation started:', data.id);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'text-delta':
|
|
132
|
+
// Text delta received, append to content
|
|
133
|
+
if (data.delta) {
|
|
134
|
+
content += data.delta;
|
|
135
|
+
onResponse(messageId, content, false);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'text-end':
|
|
140
|
+
// Text generation ended
|
|
141
|
+
console.log('Text generation ended:', data.id);
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case 'finish-step':
|
|
145
|
+
// Step finished, no action needed
|
|
146
|
+
console.log('Step finished');
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case 'finish':
|
|
150
|
+
// Stream finished
|
|
151
|
+
console.log('Stream finished');
|
|
152
|
+
done = true;
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
// Additional message types that might be present in the AI library
|
|
156
|
+
case 'tool-call':
|
|
157
|
+
// Tool call initiated
|
|
158
|
+
console.log('Tool call initiated:', data);
|
|
159
|
+
isToolCallMode = true;
|
|
160
|
+
if (data.toolCallId && data.toolName && data.args) {
|
|
161
|
+
toolInvocations.push({
|
|
162
|
+
toolCallId: data.toolCallId,
|
|
163
|
+
toolName: data.toolName,
|
|
164
|
+
args: data.args
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case 'tool-call-delta':
|
|
170
|
+
// Tool call delta (for streaming tool calls)
|
|
171
|
+
console.log('Tool call delta:', data);
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case 'tool-call-end':
|
|
175
|
+
// Tool call completed
|
|
176
|
+
console.log('Tool call completed:', data);
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case 'tool-result':
|
|
180
|
+
// Tool execution result
|
|
181
|
+
console.log('Tool result:', data);
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case 'error':
|
|
185
|
+
// Error occurred
|
|
186
|
+
console.error('Stream error:', data);
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
case 'usage':
|
|
190
|
+
// Usage information
|
|
191
|
+
console.log('Usage info:', data);
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case 'model':
|
|
195
|
+
// Model information
|
|
196
|
+
console.log('Model info:', data);
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'stop':
|
|
200
|
+
// Stop signal
|
|
201
|
+
console.log('Stop signal received');
|
|
202
|
+
done = true;
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
// Unknown type, log for debugging
|
|
207
|
+
console.log('Unknown stream type:', data.type, data);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Error parsing stream data:', error, dataStr);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (readerDone) {
|
|
218
|
+
done = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check if we have content or if this was a tool call response
|
|
223
|
+
if (content || toolInvocations.length > 0) {
|
|
224
|
+
currentMessages.push({
|
|
225
|
+
id: messageId,
|
|
226
|
+
role: "assistant",
|
|
227
|
+
content: content,
|
|
228
|
+
toolCalls: toolInvocations.length > 0 ? toolInvocations: undefined,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Handle tool call scenario if tools were provided
|
|
233
|
+
if (tools.length > 0 && toolInvocations.length > 0) {
|
|
234
|
+
console.log('Tool calls detected, executing tools...');
|
|
235
|
+
|
|
236
|
+
const toolResults: Message[] = [];
|
|
237
|
+
for (const toolInvocation of toolInvocations) {
|
|
238
|
+
const tool = tools.find(t => t.name === toolInvocation.toolName);
|
|
239
|
+
if (tool && tool.execute) {
|
|
240
|
+
try {
|
|
241
|
+
const result = await tool.execute(toolInvocation.args);
|
|
242
|
+
toolResults.push({
|
|
243
|
+
id: Math.random().toString(36).substring(3),
|
|
244
|
+
role: "user",
|
|
245
|
+
content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
|
|
249
|
+
toolResults.push({
|
|
250
|
+
id: Math.random().toString(36).substring(3),
|
|
251
|
+
role: "user",
|
|
252
|
+
content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (toolResults.length > 0) {
|
|
259
|
+
currentMessages.push(...toolResults);
|
|
260
|
+
// Continue the loop to handle the next response
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Since the new format doesn't seem to support tool calls in the same way,
|
|
266
|
+
// we'll assume the stream is complete when we reach the end
|
|
267
|
+
// If tools are provided and no content was generated, this might indicate a tool call
|
|
268
|
+
if (tools.length > 0 && !content && !isToolCallMode) {
|
|
269
|
+
// This might be a tool call scenario, but we need more information
|
|
270
|
+
// For now, we'll just finish the stream
|
|
271
|
+
console.log('No content generated, but tools provided - might be tool call scenario');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
onResponse(messageId, content, true, toolInvocations);
|
|
275
|
+
return;
|
|
276
|
+
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('Error in streamChatGPT:', error);
|
|
279
|
+
onResponse(messageId, `Error: ${error instanceof Error ? error.message : String(error)}`, true, []);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
type PrimitiveType = 'string' | 'number' | 'boolean';
|
|
2
|
+
|
|
3
|
+
// This is the type that can appear in the `type` property
|
|
4
|
+
type ObjectToolParameterType =
|
|
5
|
+
| PrimitiveType
|
|
6
|
+
| { [key: string]: ObjectToolParameter } // for nested objects
|
|
7
|
+
| [{ [key: string]: ObjectToolParameter }]; // for arrays of objects (notice the tuple type)
|
|
8
|
+
|
|
9
|
+
interface ObjectToolParameter {
|
|
10
|
+
type: ObjectToolParameterType;
|
|
11
|
+
description?: string;
|
|
12
|
+
enum?: string[];
|
|
13
|
+
optional?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The tools that the AI can use.
|
|
18
|
+
*
|
|
19
|
+
* The key is the name of the tool.
|
|
20
|
+
* The value is the parameter of the tool.
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
export type ObjectTool = {
|
|
24
|
+
[key: string]: ObjectToolParameter;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface ObjectRequest {
|
|
28
|
+
/**
|
|
29
|
+
* The tools that the AI can use.
|
|
30
|
+
*/
|
|
31
|
+
tool: ObjectTool;
|
|
32
|
+
/**
|
|
33
|
+
* High level instructions for the AI to follow. Behaviour, tone, restrictions, etc.
|
|
34
|
+
* Example: "Act like a recipe writer."
|
|
35
|
+
*/
|
|
36
|
+
behaviour?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The specific instruction for the AI to follow.
|
|
39
|
+
* Example: "Generate a recipe using chicken, rice and vegetables."
|
|
40
|
+
*/
|
|
41
|
+
instructions: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function generateObject(backendUrl: string, request: ObjectRequest, token: string) {
|
|
45
|
+
return await fetch(`${backendUrl}/ai/llm-object`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
stream: false,
|
|
49
|
+
tool: request.tool,
|
|
50
|
+
behaviour: request.behaviour,
|
|
51
|
+
instructions: request.instructions,
|
|
52
|
+
}),
|
|
53
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
54
|
+
}).then(response => response.json());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// TODO adjust stream to work with object
|
|
58
|
+
export type OnLLMResponse = (id: string, response: string, finished: boolean, toolInvocations?: any[]) => void;
|
|
59
|
+
|
|
60
|
+
export async function streamObject(backendUrl: string, request: ObjectRequest, onResponse: OnLLMResponse, token: string) {
|
|
61
|
+
const messageId = Math.random().toString(36).substring(3);
|
|
62
|
+
const response = await fetch(`${backendUrl}/ai/llm-object`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
stream: true,
|
|
66
|
+
tools: request.tool,
|
|
67
|
+
systemInstructions: request.behaviour,
|
|
68
|
+
secondaryInstructions: request.instructions,
|
|
69
|
+
}),
|
|
70
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!response.body) {
|
|
74
|
+
console.error('No response body.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const reader = response.body.getReader();
|
|
79
|
+
const decoder = new TextDecoder('utf-8');
|
|
80
|
+
|
|
81
|
+
let content = "";
|
|
82
|
+
let done = false;
|
|
83
|
+
let toolInvocations: any[] = [];
|
|
84
|
+
while (!done) {
|
|
85
|
+
const { value } = await reader.read();
|
|
86
|
+
|
|
87
|
+
if (value) {
|
|
88
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
89
|
+
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
|
90
|
+
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
const data = line.substring(3, line.length - 1);
|
|
93
|
+
const command = line.substring(0, 1);
|
|
94
|
+
// console.log("data: ", { line, data, command });
|
|
95
|
+
|
|
96
|
+
if (command === '0') {
|
|
97
|
+
content += data;
|
|
98
|
+
// console.log("AI response:", content);
|
|
99
|
+
|
|
100
|
+
//content \n\n should be real line break when message is displayed
|
|
101
|
+
onResponse(messageId, content.replace(/\\n/g, '\n').replace(/\\+"/g, '"'), false);
|
|
102
|
+
} else if (command === 'd') {
|
|
103
|
+
// console.log("AI usage:", JSON.parse(line.substring(2)));
|
|
104
|
+
done = true;
|
|
105
|
+
break;
|
|
106
|
+
} else if (command === '9') {
|
|
107
|
+
// console.log("tool call:", JSON.parse(line.substring(2)));
|
|
108
|
+
// console.log("tools", tools);
|
|
109
|
+
toolInvocations.push(JSON.parse(line.substring(2)));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
onResponse(messageId, content.replace(/\\n/g, '\n').replace(/\\+"/g, '"'), true, toolInvocations);
|
|
115
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
import { LanguageLevel } from "
|
|
3
|
-
import { Language } from "
|
|
2
|
+
import { LanguageLevel } from "../../utils/difficultyConverter";
|
|
3
|
+
import { Language } from "../../utils/Language";
|
|
4
|
+
|
|
5
|
+
export interface Buddy {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
avatarUrl: string;
|
|
10
|
+
voiceId: string;
|
|
11
|
+
aiPersonality: string;
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
export interface UserInfo {
|
|
6
15
|
skill_level_reading: LanguageLevel;
|
|
@@ -11,13 +20,30 @@ export interface UserInfo {
|
|
|
11
20
|
skill_level_understanding: LanguageLevel;
|
|
12
21
|
goal_longterm: string;
|
|
13
22
|
goal_weekly: string;
|
|
14
|
-
study_buddy:
|
|
23
|
+
study_buddy: Buddy;
|
|
15
24
|
story_genre: string;
|
|
16
25
|
study_duration: number;
|
|
26
|
+
/**
|
|
27
|
+
* The 2 letter language code of the language the user speaks natively.
|
|
28
|
+
* With the function getLanguageName, the language name can be retrieved.
|
|
29
|
+
*/
|
|
17
30
|
mother_tongue: Language;
|
|
31
|
+
/**
|
|
32
|
+
* The language the user targets to learn.
|
|
33
|
+
*/
|
|
34
|
+
target_language: Language;
|
|
18
35
|
motivation_type: string;
|
|
19
36
|
onboarding_completed: boolean;
|
|
20
37
|
context_menu_on_select: boolean;
|
|
38
|
+
user_name?: string;
|
|
39
|
+
/**
|
|
40
|
+
* ISO 3166-1 alpha-2 country code of user's location (exposed to plugins)
|
|
41
|
+
*/
|
|
42
|
+
location_country: string;
|
|
43
|
+
/**
|
|
44
|
+
* Optional: nearest big city (>100,000) near user's location
|
|
45
|
+
*/
|
|
46
|
+
location_city?: string;
|
|
21
47
|
}
|
|
22
48
|
|
|
23
49
|
export class SettingsController {
|
|
@@ -43,32 +69,6 @@ export class SettingsController {
|
|
|
43
69
|
await this.supabase.from("plugin_settings").upsert({ plugin_id: this.pluginId, settings });
|
|
44
70
|
}
|
|
45
71
|
|
|
46
|
-
public async getUserInfo(): Promise<UserInfo> {
|
|
47
|
-
const { data } = await this.supabase.from("profiles").select("*");
|
|
48
|
-
|
|
49
|
-
if (!data || data.length === 0) {
|
|
50
|
-
return {
|
|
51
|
-
mother_tongue: "en",
|
|
52
|
-
skill_level_listening: "Pre-A1",
|
|
53
|
-
skill_level_reading: "Pre-A1",
|
|
54
|
-
skill_level_speaking: "Pre-A1",
|
|
55
|
-
skill_level_writing: "Pre-A1",
|
|
56
|
-
skill_level_understanding: "Pre-A1",
|
|
57
|
-
skill_level_grammar: "Pre-A1",
|
|
58
|
-
goal_longterm: "",
|
|
59
|
-
goal_weekly: "",
|
|
60
|
-
study_buddy: "clarence",
|
|
61
|
-
story_genre: "adventure",
|
|
62
|
-
study_duration: 30,
|
|
63
|
-
motivation_type: "self-motivated",
|
|
64
|
-
onboarding_completed: false,
|
|
65
|
-
context_menu_on_select: false,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return data[0].settings;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
72
|
/**
|
|
73
73
|
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
74
74
|
* @param defaultSettings The default settings to use if no settings are found.
|