@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,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useRef } from
|
|
3
|
-
import { EventBus } from
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { EventBus } from '../../fromRimori/EventBus';
|
|
4
4
|
const ContextMenu = ({ client }) => {
|
|
5
5
|
const [isOpen, setIsOpen] = useState(false);
|
|
6
6
|
const [actions, setActions] = useState([]);
|
|
@@ -24,16 +24,19 @@ const ContextMenu = ({ client }) => {
|
|
|
24
24
|
const range = selection.getRangeAt(0);
|
|
25
25
|
const rect = range.getBoundingClientRect();
|
|
26
26
|
// Center horizontally over the selected text, accounting for menu width
|
|
27
|
-
const centerX = rect.left +
|
|
27
|
+
const centerX = rect.left + rect.width / 2 - menuWidth / 2;
|
|
28
28
|
// Position 12px below where the text ends vertically
|
|
29
29
|
const textEndY = rect.bottom + 12;
|
|
30
30
|
return { x: centerX, y: textEndY, text: selectedText };
|
|
31
31
|
};
|
|
32
32
|
useEffect(() => {
|
|
33
|
-
const actions = client.plugin
|
|
33
|
+
const actions = client.plugin
|
|
34
|
+
.getPluginInfo()
|
|
35
|
+
.installedPlugins.flatMap((p) => p.context_menu_actions)
|
|
36
|
+
.filter(Boolean);
|
|
34
37
|
setActions(actions);
|
|
35
38
|
setOpenOnTextSelect(client.plugin.getUserInfo().context_menu_on_select);
|
|
36
|
-
EventBus.on(
|
|
39
|
+
EventBus.on('global.contextMenu.createActions', ({ data }) => {
|
|
37
40
|
setActions([...data.actions, ...actions]);
|
|
38
41
|
});
|
|
39
42
|
}, []);
|
|
@@ -67,8 +70,8 @@ const ContextMenu = ({ client }) => {
|
|
|
67
70
|
}
|
|
68
71
|
// Prevent context menu on textarea or text input selection
|
|
69
72
|
const target = e.target;
|
|
70
|
-
const isTextInput = target &&
|
|
71
|
-
(target.tagName === 'INPUT' && target.type === 'text'));
|
|
73
|
+
const isTextInput = target &&
|
|
74
|
+
(target.tagName === 'TEXTAREA' || (target.tagName === 'INPUT' && target.type === 'text'));
|
|
72
75
|
if (isTextInput) {
|
|
73
76
|
setIsOpen(false);
|
|
74
77
|
return;
|
|
@@ -105,15 +108,15 @@ const ContextMenu = ({ client }) => {
|
|
|
105
108
|
setPosition(calculateMobilePosition(selectedText, menuWidth));
|
|
106
109
|
}
|
|
107
110
|
};
|
|
108
|
-
document.addEventListener(
|
|
109
|
-
window.addEventListener(
|
|
110
|
-
document.addEventListener(
|
|
111
|
-
document.addEventListener(
|
|
111
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
112
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
113
|
+
document.addEventListener('contextmenu', handleMouseUp);
|
|
114
|
+
document.addEventListener('selectionchange', handleSelectionChange);
|
|
112
115
|
return () => {
|
|
113
|
-
document.removeEventListener(
|
|
114
|
-
window.removeEventListener(
|
|
115
|
-
document.removeEventListener(
|
|
116
|
-
document.removeEventListener(
|
|
116
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
117
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
118
|
+
document.removeEventListener('contextmenu', handleMouseUp);
|
|
119
|
+
document.removeEventListener('selectionchange', handleSelectionChange);
|
|
117
120
|
};
|
|
118
121
|
}, [openOnTextSelect, isOpen, position.text]);
|
|
119
122
|
if (!isOpen) {
|
|
@@ -127,6 +130,6 @@ const ContextMenu = ({ client }) => {
|
|
|
127
130
|
} }, index))) }));
|
|
128
131
|
};
|
|
129
132
|
function MenuEntryItem(props) {
|
|
130
|
-
return _jsxs("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", children: [_jsx("span", { className: "flex-grow", children: props.icon }), _jsx("span", { className: "flex-grow", children: props.text })] });
|
|
133
|
+
return (_jsxs("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", children: [_jsx("span", { className: "flex-grow", children: props.icon }), _jsx("span", { className: "flex-grow", children: props.text })] }));
|
|
131
134
|
}
|
|
132
135
|
export default ContextMenu;
|
package/dist/components.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export * from
|
|
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
|
|
1
|
+
export * from './components/ai/Assistant';
|
|
2
|
+
export * from './components/ai/Avatar';
|
|
3
|
+
export * from './components/ai/EmbeddedAssistent/VoiceRecoder';
|
|
4
|
+
export * from './components/audio/Playbutton';
|
|
5
|
+
export * from './components/CRUDModal';
|
|
6
|
+
export * from './components/MarkdownEditor';
|
|
7
|
+
export * from './components/Spinner';
|
|
8
|
+
export * from './hooks/UseChatHook';
|
|
9
|
+
export * from './plugin/ThemeSetter';
|
|
10
|
+
export * from './providers/PluginProvider';
|
package/dist/components.js
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
|
export interface ToolInvocation {
|
|
3
3
|
toolCallId: string;
|
|
4
4
|
toolName: string;
|
|
@@ -6,7 +6,7 @@ export interface ToolInvocation {
|
|
|
6
6
|
}
|
|
7
7
|
export interface Message {
|
|
8
8
|
id?: string;
|
|
9
|
-
role:
|
|
9
|
+
role: 'user' | 'assistant' | 'system';
|
|
10
10
|
content: string;
|
|
11
11
|
toolCalls?: ToolInvocation[];
|
|
12
12
|
}
|
|
@@ -23,7 +23,7 @@ export function generateText(backendUrl, messages, tools, token) {
|
|
|
23
23
|
const response = yield fetch(`${backendUrl}/ai/llm`, {
|
|
24
24
|
method: 'POST',
|
|
25
25
|
body: JSON.stringify({ messages, tools }),
|
|
26
|
-
headers: {
|
|
26
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
27
27
|
});
|
|
28
28
|
return yield response.json();
|
|
29
29
|
});
|
|
@@ -36,7 +36,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
36
36
|
messageId,
|
|
37
37
|
messageCount: messages.length,
|
|
38
38
|
toolCount: tools.length,
|
|
39
|
-
backendUrl
|
|
39
|
+
backendUrl,
|
|
40
40
|
});
|
|
41
41
|
while (true) {
|
|
42
42
|
const messagesForApi = currentMessages.map((_a) => {
|
|
@@ -47,7 +47,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
47
47
|
const response = yield fetch(`${backendUrl}/ai/llm`, {
|
|
48
48
|
method: 'POST',
|
|
49
49
|
body: JSON.stringify({ messages: messagesForApi, tools, stream: true }),
|
|
50
|
-
headers: {
|
|
50
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
51
51
|
});
|
|
52
52
|
if (!response.ok) {
|
|
53
53
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -58,12 +58,12 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
58
58
|
}
|
|
59
59
|
const reader = response.body.getReader();
|
|
60
60
|
const decoder = new TextDecoder('utf-8');
|
|
61
|
-
let content =
|
|
61
|
+
let content = '';
|
|
62
62
|
let done = false;
|
|
63
63
|
let toolInvocations = [];
|
|
64
|
-
let currentTextId =
|
|
64
|
+
let currentTextId = '';
|
|
65
65
|
let isToolCallMode = false;
|
|
66
|
-
let buffer =
|
|
66
|
+
let buffer = ''; // Buffer for incomplete chunks
|
|
67
67
|
while (!done) {
|
|
68
68
|
const { value, done: readerDone } = yield reader.read();
|
|
69
69
|
if (value) {
|
|
@@ -73,7 +73,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
73
73
|
const lines = buffer.split('\n');
|
|
74
74
|
// Keep the last line in buffer if it's incomplete
|
|
75
75
|
if (lines.length > 1) {
|
|
76
|
-
buffer = lines.pop() ||
|
|
76
|
+
buffer = lines.pop() || '';
|
|
77
77
|
}
|
|
78
78
|
for (const line of lines) {
|
|
79
79
|
if (line.trim() === '')
|
|
@@ -144,7 +144,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
144
144
|
toolInvocations.push({
|
|
145
145
|
toolCallId: data.toolCallId,
|
|
146
146
|
toolName: data.toolName,
|
|
147
|
-
args: data.args || data.input
|
|
147
|
+
args: data.args || data.input,
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
break;
|
|
@@ -198,7 +198,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
198
198
|
if (content || toolInvocations.length > 0) {
|
|
199
199
|
currentMessages.push({
|
|
200
200
|
id: messageId,
|
|
201
|
-
role:
|
|
201
|
+
role: 'assistant',
|
|
202
202
|
content: content,
|
|
203
203
|
toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
|
|
204
204
|
});
|
|
@@ -208,13 +208,13 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
208
208
|
console.log('Tool calls detected, executing tools...');
|
|
209
209
|
const toolResults = [];
|
|
210
210
|
for (const toolInvocation of toolInvocations) {
|
|
211
|
-
const tool = tools.find(t => t.name === toolInvocation.toolName);
|
|
211
|
+
const tool = tools.find((t) => t.name === toolInvocation.toolName);
|
|
212
212
|
if (tool && tool.execute) {
|
|
213
213
|
try {
|
|
214
214
|
const result = yield tool.execute(toolInvocation.args);
|
|
215
215
|
toolResults.push({
|
|
216
216
|
id: Math.random().toString(36).substring(3),
|
|
217
|
-
role:
|
|
217
|
+
role: 'user',
|
|
218
218
|
content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
|
|
219
219
|
});
|
|
220
220
|
}
|
|
@@ -222,7 +222,7 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
|
|
|
222
222
|
console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
|
|
223
223
|
toolResults.push({
|
|
224
224
|
id: Math.random().toString(36).substring(3),
|
|
225
|
-
role:
|
|
225
|
+
role: 'user',
|
|
226
226
|
content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
|
|
227
227
|
});
|
|
228
228
|
}
|
|
@@ -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
|
export type TriggerAction = {
|
|
4
4
|
action_key: string;
|
|
5
5
|
} & Record<string, string | number | boolean>;
|
|
@@ -38,7 +38,7 @@ export class ExerciseController {
|
|
|
38
38
|
method: 'POST',
|
|
39
39
|
headers: {
|
|
40
40
|
'Content-Type': 'application/json',
|
|
41
|
-
|
|
41
|
+
Authorization: `Bearer ${token}`,
|
|
42
42
|
},
|
|
43
43
|
body: JSON.stringify(params),
|
|
44
44
|
});
|
|
@@ -60,7 +60,7 @@ export class ExerciseController {
|
|
|
60
60
|
const response = yield fetch(`${this.pluginController.getBackendUrl()}/exercises/${id}`, {
|
|
61
61
|
method: 'DELETE',
|
|
62
62
|
headers: {
|
|
63
|
-
|
|
63
|
+
Authorization: `Bearer ${token}`,
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
if (!response.ok) {
|
|
@@ -17,8 +17,8 @@ export function generateObject(backendUrl, request, token) {
|
|
|
17
17
|
behaviour: request.behaviour,
|
|
18
18
|
instructions: request.instructions,
|
|
19
19
|
}),
|
|
20
|
-
headers: {
|
|
21
|
-
}).then(response => response.json());
|
|
20
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
21
|
+
}).then((response) => response.json());
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
export function streamObject(backendUrl, request, onResponse, token) {
|
|
@@ -32,7 +32,7 @@ export function streamObject(backendUrl, request, onResponse, token) {
|
|
|
32
32
|
systemInstructions: request.behaviour,
|
|
33
33
|
secondaryInstructions: request.instructions,
|
|
34
34
|
}),
|
|
35
|
-
headers: {
|
|
35
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
36
36
|
});
|
|
37
37
|
if (!response.body) {
|
|
38
38
|
console.error('No response body.');
|
|
@@ -40,14 +40,14 @@ export function streamObject(backendUrl, request, onResponse, token) {
|
|
|
40
40
|
}
|
|
41
41
|
const reader = response.body.getReader();
|
|
42
42
|
const decoder = new TextDecoder('utf-8');
|
|
43
|
-
let content =
|
|
43
|
+
let content = '';
|
|
44
44
|
let done = false;
|
|
45
45
|
let toolInvocations = [];
|
|
46
46
|
while (!done) {
|
|
47
47
|
const { value } = yield reader.read();
|
|
48
48
|
if (value) {
|
|
49
49
|
const chunk = decoder.decode(value, { stream: true });
|
|
50
|
-
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
|
50
|
+
const lines = chunk.split('\n').filter((line) => line.trim() !== '');
|
|
51
51
|
for (const line of lines) {
|
|
52
52
|
const data = line.substring(3, line.length - 1);
|
|
53
53
|
const command = line.substring(0, 1);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { SupabaseClient } from
|
|
2
|
-
import { LanguageLevel } from
|
|
3
|
-
import { Language } from
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { LanguageLevel } from '../../utils/difficultyConverter';
|
|
3
|
+
import { Language } from '../../utils/Language';
|
|
4
|
+
import { Guild } from '../core';
|
|
4
5
|
export interface Buddy {
|
|
5
6
|
id: string;
|
|
6
7
|
name: string;
|
|
@@ -35,19 +36,33 @@ export interface UserInfo {
|
|
|
35
36
|
context_menu_on_select: boolean;
|
|
36
37
|
user_name?: string;
|
|
37
38
|
/**
|
|
38
|
-
* ISO 3166-1 alpha-2 country code of user's location (exposed to plugins)
|
|
39
|
+
* ISO 3166-1 alpha-2 country code of user's target location (exposed to plugins)
|
|
39
40
|
*/
|
|
40
|
-
|
|
41
|
+
target_country: string;
|
|
41
42
|
/**
|
|
42
43
|
* Optional: nearest big city (>100,000) near user's location
|
|
43
44
|
*/
|
|
44
|
-
|
|
45
|
+
target_city?: string;
|
|
45
46
|
}
|
|
46
47
|
export declare class SettingsController {
|
|
47
48
|
private pluginId;
|
|
48
49
|
private supabase;
|
|
49
|
-
|
|
50
|
+
private guild;
|
|
51
|
+
constructor(supabase: SupabaseClient, pluginId: string, guild: Guild);
|
|
52
|
+
/**
|
|
53
|
+
* Fetches settings based on guild configuration.
|
|
54
|
+
* If guild doesn't allow user settings, fetches guild-level settings.
|
|
55
|
+
* Otherwise, fetches user-specific settings.
|
|
56
|
+
* @returns The settings object or null if not found.
|
|
57
|
+
*/
|
|
50
58
|
private fetchSettings;
|
|
59
|
+
/**
|
|
60
|
+
* Sets settings for the plugin.
|
|
61
|
+
* Automatically saves as guild settings if guild doesn't allow user settings,
|
|
62
|
+
* otherwise saves as user-specific settings.
|
|
63
|
+
* @param settings - The settings object to save.
|
|
64
|
+
* @throws {Error} if RLS blocks the operation.
|
|
65
|
+
*/
|
|
51
66
|
setSettings(settings: any): Promise<void>;
|
|
52
67
|
/**
|
|
53
68
|
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
@@ -8,22 +8,87 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
export class SettingsController {
|
|
11
|
-
constructor(supabase, pluginId) {
|
|
11
|
+
constructor(supabase, pluginId, guild) {
|
|
12
12
|
this.supabase = supabase;
|
|
13
13
|
this.pluginId = pluginId;
|
|
14
|
+
this.guild = guild;
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Fetches settings based on guild configuration.
|
|
18
|
+
* If guild doesn't allow user settings, fetches guild-level settings.
|
|
19
|
+
* Otherwise, fetches user-specific settings.
|
|
20
|
+
* @returns The settings object or null if not found.
|
|
21
|
+
*/
|
|
15
22
|
fetchSettings() {
|
|
16
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
var _a;
|
|
25
|
+
const isGuildSetting = !this.guild.allowUserPluginSettings;
|
|
26
|
+
const { data } = yield this.supabase
|
|
27
|
+
.from('plugin_settings')
|
|
28
|
+
.select('*')
|
|
29
|
+
.eq('plugin_id', this.pluginId)
|
|
30
|
+
.eq('guild_id', this.guild.id)
|
|
31
|
+
.eq('is_guild_setting', isGuildSetting)
|
|
32
|
+
.maybeSingle();
|
|
33
|
+
return (_a = data === null || data === void 0 ? void 0 : data.settings) !== null && _a !== void 0 ? _a : null;
|
|
22
34
|
});
|
|
23
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Sets settings for the plugin.
|
|
38
|
+
* Automatically saves as guild settings if guild doesn't allow user settings,
|
|
39
|
+
* otherwise saves as user-specific settings.
|
|
40
|
+
* @param settings - The settings object to save.
|
|
41
|
+
* @throws {Error} if RLS blocks the operation.
|
|
42
|
+
*/
|
|
24
43
|
setSettings(settings) {
|
|
25
44
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
-
|
|
45
|
+
var _a;
|
|
46
|
+
const isGuildSetting = !this.guild.allowUserPluginSettings;
|
|
47
|
+
const payload = {
|
|
48
|
+
plugin_id: this.pluginId,
|
|
49
|
+
settings,
|
|
50
|
+
guild_id: this.guild.id,
|
|
51
|
+
is_guild_setting: isGuildSetting,
|
|
52
|
+
};
|
|
53
|
+
if (isGuildSetting) {
|
|
54
|
+
payload.user_id = null;
|
|
55
|
+
}
|
|
56
|
+
// Try UPDATE first (safe with RLS). If nothing updated, INSERT.
|
|
57
|
+
const updateQuery = this.supabase
|
|
58
|
+
.from('plugin_settings')
|
|
59
|
+
.update({ settings })
|
|
60
|
+
.eq('plugin_id', this.pluginId)
|
|
61
|
+
.eq('guild_id', this.guild.id)
|
|
62
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
63
|
+
const { data: updatedRows, error: updateError } = yield (isGuildSetting
|
|
64
|
+
? updateQuery.is('user_id', null).select('id')
|
|
65
|
+
: updateQuery.select('id'));
|
|
66
|
+
if (updateError) {
|
|
67
|
+
if (updateError.code === '42501' || ((_a = updateError.message) === null || _a === void 0 ? void 0 : _a.includes('policy'))) {
|
|
68
|
+
throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
|
|
69
|
+
}
|
|
70
|
+
// proceed to try insert in case of other issues
|
|
71
|
+
}
|
|
72
|
+
if (updatedRows && updatedRows.length > 0) {
|
|
73
|
+
return; // updated successfully
|
|
74
|
+
}
|
|
75
|
+
// No row updated -> INSERT
|
|
76
|
+
const { error: insertError } = yield this.supabase.from('plugin_settings').insert(payload);
|
|
77
|
+
if (insertError) {
|
|
78
|
+
// In case of race condition (duplicate), try one more UPDATE
|
|
79
|
+
if (insertError.code === '23505' /* unique_violation */) {
|
|
80
|
+
const retry = this.supabase
|
|
81
|
+
.from('plugin_settings')
|
|
82
|
+
.update({ settings })
|
|
83
|
+
.eq('plugin_id', this.pluginId)
|
|
84
|
+
.eq('guild_id', this.guild.id)
|
|
85
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
86
|
+
const { error: retryError } = yield (isGuildSetting ? retry.is('user_id', null) : retry);
|
|
87
|
+
if (!retryError)
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
throw insertError;
|
|
91
|
+
}
|
|
27
92
|
});
|
|
28
93
|
}
|
|
29
94
|
/**
|
|
@@ -33,7 +98,7 @@ export class SettingsController {
|
|
|
33
98
|
*/
|
|
34
99
|
getSettings(defaultSettings) {
|
|
35
100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
const storedSettings = yield this.fetchSettings();
|
|
101
|
+
const storedSettings = (yield this.fetchSettings());
|
|
37
102
|
if (!storedSettings) {
|
|
38
103
|
yield this.setSettings(defaultSettings);
|
|
39
104
|
return defaultSettings;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import { RimoriClient } from
|
|
3
|
-
import { ObjectRequest } from
|
|
2
|
+
import { RimoriClient } from '../../plugin/RimoriClient';
|
|
3
|
+
import { ObjectRequest } from './ObjectController';
|
|
4
4
|
export interface SharedContentObjectRequest extends ObjectRequest {
|
|
5
5
|
fixedProperties?: Record<string, string | number | boolean>;
|
|
6
6
|
}
|
|
@@ -69,7 +69,7 @@ export declare class SharedContentController {
|
|
|
69
69
|
* @returns The inserted shared content.
|
|
70
70
|
* @throws {Error} if insertion fails.
|
|
71
71
|
*/
|
|
72
|
-
createSharedContent<T>({ contentType, title, keywords, data, privateTopic }: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>>;
|
|
72
|
+
createSharedContent<T>({ contentType, title, keywords, data, privateTopic, }: Omit<SharedContent<T>, 'id'>): Promise<SharedContent<T>>;
|
|
73
73
|
/**
|
|
74
74
|
* Update existing shared content in the database.
|
|
75
75
|
* @param id - The ID of the content to update.
|
|
@@ -28,8 +28,9 @@ export class SharedContentController {
|
|
|
28
28
|
//this filter is there if the content should be filtered additionally by a column and value
|
|
29
29
|
filter, options) {
|
|
30
30
|
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
-
let query = this.supabase
|
|
32
|
-
.
|
|
31
|
+
let query = this.supabase
|
|
32
|
+
.from('shared_content')
|
|
33
|
+
.select('*, scc:shared_content_completed(id, state)')
|
|
33
34
|
.eq('content_type', contentType)
|
|
34
35
|
.not('scc.state', 'in', '("completed","ongoing","hidden")')
|
|
35
36
|
.is('deleted_at', null);
|
|
@@ -56,7 +57,7 @@ export class SharedContentController {
|
|
|
56
57
|
console.log('instructions:', instructions);
|
|
57
58
|
//create the shared content object
|
|
58
59
|
const data = {
|
|
59
|
-
id:
|
|
60
|
+
id: 'internal-temp-id-' + Math.random().toString(36).substring(2, 15),
|
|
60
61
|
contentType,
|
|
61
62
|
title: instructions.title,
|
|
62
63
|
keywords: instructions.keywords.map(({ text }) => text),
|
|
@@ -82,20 +83,21 @@ export class SharedContentController {
|
|
|
82
83
|
generatorInstructions.instructions += `
|
|
83
84
|
The following topics are already taken: ${completedTopics.join(', ')}`;
|
|
84
85
|
generatorInstructions.tool.title = {
|
|
85
|
-
type:
|
|
86
|
-
description:
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'What the topic is about. Short. ',
|
|
87
88
|
};
|
|
88
89
|
generatorInstructions.tool.keywords = {
|
|
89
|
-
type: [{ text: { type:
|
|
90
|
-
description:
|
|
90
|
+
type: [{ text: { type: 'string' } }],
|
|
91
|
+
description: 'Keywords around the topic of the assignment.',
|
|
91
92
|
};
|
|
92
93
|
return generatorInstructions;
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
getCompletedTopics(contentType, filter) {
|
|
96
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
const query = this.supabase
|
|
98
|
-
.
|
|
98
|
+
const query = this.supabase
|
|
99
|
+
.from('shared_content')
|
|
100
|
+
.select('title, keywords, scc:shared_content_completed(id)')
|
|
99
101
|
.eq('content_type', contentType)
|
|
100
102
|
.not('scc.id', 'is', null)
|
|
101
103
|
.is('deleted_at', null);
|
|
@@ -112,7 +114,13 @@ export class SharedContentController {
|
|
|
112
114
|
}
|
|
113
115
|
getSharedContent(contentType, id) {
|
|
114
116
|
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
-
const { data, error } = yield this.supabase
|
|
117
|
+
const { data, error } = yield this.supabase
|
|
118
|
+
.from('shared_content')
|
|
119
|
+
.select()
|
|
120
|
+
.eq('content_type', contentType)
|
|
121
|
+
.eq('id', id)
|
|
122
|
+
.is('deleted_at', null)
|
|
123
|
+
.single();
|
|
116
124
|
if (error) {
|
|
117
125
|
console.error('error fetching shared content:', error);
|
|
118
126
|
throw new Error('error fetching shared content');
|
|
@@ -124,7 +132,7 @@ export class SharedContentController {
|
|
|
124
132
|
return __awaiter(this, void 0, void 0, function* () {
|
|
125
133
|
// Idempotent completion: upsert on (id, user_id) so repeated calls don't fail
|
|
126
134
|
const { error } = yield this.supabase
|
|
127
|
-
.from(
|
|
135
|
+
.from('shared_content_completed')
|
|
128
136
|
.upsert({ content_type: contentType, id: assignmentId }, { onConflict: 'id' });
|
|
129
137
|
if (error) {
|
|
130
138
|
console.error('error completing shared content:', error);
|
|
@@ -153,9 +161,7 @@ export class SharedContentController {
|
|
|
153
161
|
if (bookmarked !== undefined)
|
|
154
162
|
payload.bookmarked = bookmarked;
|
|
155
163
|
// Prefer upsert, fall back to insert/update if upsert not allowed
|
|
156
|
-
const { error } = yield this.supabase
|
|
157
|
-
.from('shared_content_completed')
|
|
158
|
-
.upsert(payload, { onConflict: 'id' });
|
|
164
|
+
const { error } = yield this.supabase.from('shared_content_completed').upsert(payload, { onConflict: 'id' });
|
|
159
165
|
if (error) {
|
|
160
166
|
console.error('error updating shared content state:', error);
|
|
161
167
|
throw new Error('error updating shared content state');
|
|
@@ -171,7 +177,12 @@ export class SharedContentController {
|
|
|
171
177
|
*/
|
|
172
178
|
getSharedContentList(contentType, filter, limit) {
|
|
173
179
|
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
-
const query = this.supabase
|
|
180
|
+
const query = this.supabase
|
|
181
|
+
.from('shared_content')
|
|
182
|
+
.select('*')
|
|
183
|
+
.eq('content_type', contentType)
|
|
184
|
+
.is('deleted_at', null)
|
|
185
|
+
.limit(limit !== null && limit !== void 0 ? limit : 30);
|
|
175
186
|
if (filter) {
|
|
176
187
|
query.contains('data', filter);
|
|
177
188
|
}
|
|
@@ -195,14 +206,17 @@ export class SharedContentController {
|
|
|
195
206
|
* @throws {Error} if insertion fails.
|
|
196
207
|
*/
|
|
197
208
|
createSharedContent(_a) {
|
|
198
|
-
return __awaiter(this, arguments, void 0, function* ({ contentType, title, keywords, data, privateTopic }) {
|
|
199
|
-
const { data: newContent, error } = yield this.supabase
|
|
209
|
+
return __awaiter(this, arguments, void 0, function* ({ contentType, title, keywords, data, privateTopic, }) {
|
|
210
|
+
const { data: newContent, error } = yield this.supabase
|
|
211
|
+
.from('shared_content')
|
|
212
|
+
.insert({
|
|
200
213
|
private: privateTopic,
|
|
201
214
|
content_type: contentType,
|
|
202
215
|
title,
|
|
203
216
|
keywords,
|
|
204
217
|
data,
|
|
205
|
-
})
|
|
218
|
+
})
|
|
219
|
+
.select();
|
|
206
220
|
if (error) {
|
|
207
221
|
console.error('error inserting shared content:', error);
|
|
208
222
|
throw new Error('error inserting shared content');
|
|
@@ -230,7 +244,11 @@ export class SharedContentController {
|
|
|
230
244
|
updateData.data = updates.data;
|
|
231
245
|
if (updates.privateTopic !== undefined)
|
|
232
246
|
updateData.private = updates.privateTopic;
|
|
233
|
-
const { data: updatedContent, error } = yield this.supabase
|
|
247
|
+
const { data: updatedContent, error } = yield this.supabase
|
|
248
|
+
.from('shared_content')
|
|
249
|
+
.update(updateData)
|
|
250
|
+
.eq('id', id)
|
|
251
|
+
.select();
|
|
234
252
|
if (error) {
|
|
235
253
|
console.error('error updating shared content:', error);
|
|
236
254
|
throw new Error('error updating shared content');
|
|
@@ -250,7 +268,7 @@ export class SharedContentController {
|
|
|
250
268
|
removeSharedContent(id) {
|
|
251
269
|
return __awaiter(this, void 0, void 0, function* () {
|
|
252
270
|
const { data: deletedContent, error } = yield this.supabase
|
|
253
|
-
.from(
|
|
271
|
+
.from('shared_content')
|
|
254
272
|
.update({ deleted_at: new Date().toISOString() })
|
|
255
273
|
.eq('id', id)
|
|
256
274
|
.select();
|
|
@@ -13,9 +13,11 @@ export function getSTTResponse(backendUrl, audio, token) {
|
|
|
13
13
|
formData.append('file', audio);
|
|
14
14
|
return yield fetch(`${backendUrl}/voice/stt`, {
|
|
15
15
|
method: 'POST',
|
|
16
|
-
headers: {
|
|
16
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
17
17
|
body: formData,
|
|
18
|
-
})
|
|
18
|
+
})
|
|
19
|
+
.then((r) => r.json())
|
|
20
|
+
.then((r) => {
|
|
19
21
|
// console.log("STT response: ", r);
|
|
20
22
|
return r.text;
|
|
21
23
|
});
|
|
@@ -27,9 +29,9 @@ export function getTTSResponse(backendUrl, request, token) {
|
|
|
27
29
|
method: 'POST',
|
|
28
30
|
headers: {
|
|
29
31
|
'Content-Type': 'application/json',
|
|
30
|
-
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
31
33
|
},
|
|
32
34
|
body: JSON.stringify(request),
|
|
33
|
-
}).then(r => r.blob());
|
|
35
|
+
}).then((r) => r.blob());
|
|
34
36
|
});
|
|
35
37
|
}
|