@rimori/client 1.3.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +35 -0
- 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 +2 -2
- package/dist/cli/scripts/release/release.js +2 -2
- package/dist/cli/types/DatabaseTypes.d.ts +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 +14 -7
- 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/TTS/Player.js +1 -1
- 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 +20 -18
- package/dist/core/controller/ExerciseController.d.ts +52 -0
- package/dist/core/controller/ExerciseController.js +73 -0
- 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 -14
- 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.d.ts +5 -0
- package/dist/plugin/Logger.js +65 -13
- package/dist/plugin/PluginController.d.ts +7 -1
- package/dist/plugin/PluginController.js +32 -27
- package/dist/plugin/RimoriClient.d.ts +39 -14
- package/dist/plugin/RimoriClient.js +60 -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/eslint.config.js +53 -0
- 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 +17 -4
- package/prettier.config.js +8 -0
- 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 +3 -3
- package/src/cli/scripts/release/release.ts +4 -4
- package/src/cli/types/DatabaseTypes.ts +7 -8
- 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 +20 -16
- 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 +177 -178
- 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 +62 -50
- package/src/core/controller/ExerciseController.ts +98 -0
- 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 -15
- 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 +77 -19
- package/src/plugin/PluginController.ts +60 -44
- package/src/plugin/RimoriClient.ts +133 -69
- 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
- package/dist/components/LoggerExample.d.ts +0 -6
- package/dist/components/LoggerExample.js +0 -79
- package/dist/core/controller/AudioController.d.ts +0 -0
- package/dist/core/controller/AudioController.js +0 -1
- package/dist/hooks/UseLogger.d.ts +0 -30
- package/dist/hooks/UseLogger.js +0 -122
- package/dist/plugin/LoggerExample.d.ts +0 -16
- package/dist/plugin/LoggerExample.js +0 -140
- package/dist/utils/audioFormats.d.ts +0 -26
- package/dist/utils/audioFormats.js +0 -67
|
@@ -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 = (
|
|
26
|
+
export type OnLLMResponse = (
|
|
27
|
+
id: string,
|
|
28
|
+
response: string,
|
|
29
|
+
finished: boolean,
|
|
30
|
+
toolInvocations?: ToolInvocation[],
|
|
31
|
+
) => void;
|
|
27
32
|
|
|
28
|
-
export async function streamChatGPT(
|
|
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();
|
|
@@ -71,22 +82,22 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
71
82
|
if (value) {
|
|
72
83
|
const chunk = decoder.decode(value, { stream: true });
|
|
73
84
|
buffer += chunk;
|
|
74
|
-
|
|
85
|
+
|
|
75
86
|
// Split by lines, but handle incomplete lines
|
|
76
87
|
const lines = buffer.split('\n');
|
|
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) {
|
|
84
95
|
if (line.trim() === '') continue;
|
|
85
|
-
|
|
96
|
+
|
|
86
97
|
// Handle the new streaming format
|
|
87
98
|
if (line.startsWith('data: ')) {
|
|
88
99
|
const dataStr = line.substring(6); // Remove 'data: ' prefix
|
|
89
|
-
|
|
100
|
+
|
|
90
101
|
// Handle [DONE] marker
|
|
91
102
|
if (dataStr === '[DONE]') {
|
|
92
103
|
done = true;
|
|
@@ -95,39 +106,39 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
95
106
|
|
|
96
107
|
try {
|
|
97
108
|
const data = JSON.parse(dataStr);
|
|
98
|
-
|
|
109
|
+
|
|
99
110
|
// Log the first message to understand the format
|
|
100
111
|
if (!content && !isToolCallMode) {
|
|
101
|
-
console.log('First stream message received:', data);
|
|
112
|
+
// console.log('First stream message received:', data);
|
|
102
113
|
}
|
|
103
|
-
|
|
114
|
+
|
|
104
115
|
switch (data.type) {
|
|
105
116
|
case 'start':
|
|
106
117
|
// Stream started, no action needed
|
|
107
|
-
console.log('Stream started');
|
|
118
|
+
// console.log('Stream started');
|
|
108
119
|
break;
|
|
109
|
-
|
|
120
|
+
|
|
110
121
|
case 'start-step':
|
|
111
122
|
// Step started, no action needed
|
|
112
|
-
console.log('Step started');
|
|
123
|
+
// console.log('Step started');
|
|
113
124
|
break;
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
case 'reasoning-start':
|
|
116
127
|
// Reasoning started, no action needed
|
|
117
128
|
console.log('Reasoning started:', data.id);
|
|
118
129
|
break;
|
|
119
|
-
|
|
130
|
+
|
|
120
131
|
case 'reasoning-end':
|
|
121
132
|
// Reasoning ended, no action needed
|
|
122
133
|
console.log('Reasoning ended:', data.id);
|
|
123
134
|
break;
|
|
124
|
-
|
|
135
|
+
|
|
125
136
|
case 'text-start':
|
|
126
137
|
// Text generation started, store the ID
|
|
127
138
|
currentTextId = data.id;
|
|
128
139
|
console.log('Text generation started:', data.id);
|
|
129
140
|
break;
|
|
130
|
-
|
|
141
|
+
|
|
131
142
|
case 'text-delta':
|
|
132
143
|
// Text delta received, append to content
|
|
133
144
|
if (data.delta) {
|
|
@@ -135,73 +146,75 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
135
146
|
onResponse(messageId, content, false);
|
|
136
147
|
}
|
|
137
148
|
break;
|
|
138
|
-
|
|
149
|
+
|
|
139
150
|
case 'text-end':
|
|
140
151
|
// Text generation ended
|
|
141
152
|
console.log('Text generation ended:', data.id);
|
|
142
153
|
break;
|
|
143
|
-
|
|
154
|
+
|
|
144
155
|
case 'finish-step':
|
|
145
156
|
// Step finished, no action needed
|
|
146
|
-
console.log('Step finished');
|
|
157
|
+
// console.log('Step finished');
|
|
147
158
|
break;
|
|
148
|
-
|
|
159
|
+
|
|
149
160
|
case 'finish':
|
|
150
161
|
// Stream finished
|
|
151
|
-
console.log('Stream finished');
|
|
162
|
+
// console.log('Stream finished');
|
|
152
163
|
done = true;
|
|
153
164
|
break;
|
|
154
|
-
|
|
165
|
+
|
|
155
166
|
// Additional message types that might be present in the AI library
|
|
156
167
|
case 'tool-call':
|
|
168
|
+
case 'tool-input-available': //for now input calls should be handled the same way as tool calls
|
|
157
169
|
// Tool call initiated
|
|
158
170
|
console.log('Tool call initiated:', data);
|
|
159
171
|
isToolCallMode = true;
|
|
160
|
-
if (data.toolCallId && data.toolName && data.args) {
|
|
172
|
+
if (data.toolCallId && data.toolName && (data.args || data.input)) {
|
|
161
173
|
toolInvocations.push({
|
|
162
174
|
toolCallId: data.toolCallId,
|
|
163
175
|
toolName: data.toolName,
|
|
164
|
-
args: data.args
|
|
176
|
+
args: data.args || data.input,
|
|
165
177
|
});
|
|
166
178
|
}
|
|
167
179
|
break;
|
|
168
|
-
|
|
180
|
+
|
|
181
|
+
case 'tool-input-delta': //for now input calls should be handled the same way as tool calls
|
|
169
182
|
case 'tool-call-delta':
|
|
170
183
|
// Tool call delta (for streaming tool calls)
|
|
171
184
|
console.log('Tool call delta:', data);
|
|
172
185
|
break;
|
|
173
|
-
|
|
186
|
+
|
|
174
187
|
case 'tool-call-end':
|
|
175
188
|
// Tool call completed
|
|
176
189
|
console.log('Tool call completed:', data);
|
|
177
190
|
break;
|
|
178
|
-
|
|
191
|
+
|
|
179
192
|
case 'tool-result':
|
|
180
193
|
// Tool execution result
|
|
181
194
|
console.log('Tool result:', data);
|
|
182
195
|
break;
|
|
183
|
-
|
|
196
|
+
|
|
184
197
|
case 'error':
|
|
185
198
|
// Error occurred
|
|
186
199
|
console.error('Stream error:', data);
|
|
187
200
|
break;
|
|
188
|
-
|
|
201
|
+
|
|
189
202
|
case 'usage':
|
|
190
203
|
// Usage information
|
|
191
204
|
console.log('Usage info:', data);
|
|
192
205
|
break;
|
|
193
|
-
|
|
206
|
+
|
|
194
207
|
case 'model':
|
|
195
208
|
// Model information
|
|
196
209
|
console.log('Model info:', data);
|
|
197
210
|
break;
|
|
198
|
-
|
|
211
|
+
|
|
199
212
|
case 'stop':
|
|
200
213
|
// Stop signal
|
|
201
214
|
console.log('Stop signal received');
|
|
202
215
|
done = true;
|
|
203
216
|
break;
|
|
204
|
-
|
|
217
|
+
|
|
205
218
|
default:
|
|
206
219
|
// Unknown type, log for debugging
|
|
207
220
|
console.log('Unknown stream type:', data.type, data);
|
|
@@ -223,38 +236,38 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
223
236
|
if (content || toolInvocations.length > 0) {
|
|
224
237
|
currentMessages.push({
|
|
225
238
|
id: messageId,
|
|
226
|
-
role:
|
|
239
|
+
role: 'assistant',
|
|
227
240
|
content: content,
|
|
228
|
-
toolCalls: toolInvocations.length > 0 ? toolInvocations: undefined,
|
|
241
|
+
toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
|
|
229
242
|
});
|
|
230
243
|
}
|
|
231
244
|
|
|
232
245
|
// Handle tool call scenario if tools were provided
|
|
233
246
|
if (tools.length > 0 && toolInvocations.length > 0) {
|
|
234
247
|
console.log('Tool calls detected, executing tools...');
|
|
235
|
-
|
|
248
|
+
|
|
236
249
|
const toolResults: Message[] = [];
|
|
237
250
|
for (const toolInvocation of toolInvocations) {
|
|
238
|
-
const tool = tools.find(t => t.name === toolInvocation.toolName);
|
|
251
|
+
const tool = tools.find((t) => t.name === toolInvocation.toolName);
|
|
239
252
|
if (tool && tool.execute) {
|
|
240
253
|
try {
|
|
241
254
|
const result = await tool.execute(toolInvocation.args);
|
|
242
255
|
toolResults.push({
|
|
243
256
|
id: Math.random().toString(36).substring(3),
|
|
244
|
-
role:
|
|
257
|
+
role: 'user',
|
|
245
258
|
content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
|
|
246
259
|
});
|
|
247
260
|
} catch (error) {
|
|
248
261
|
console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
|
|
249
262
|
toolResults.push({
|
|
250
263
|
id: Math.random().toString(36).substring(3),
|
|
251
|
-
role:
|
|
264
|
+
role: 'user',
|
|
252
265
|
content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
|
|
253
266
|
});
|
|
254
267
|
}
|
|
255
268
|
}
|
|
256
269
|
}
|
|
257
|
-
|
|
270
|
+
|
|
258
271
|
if (toolResults.length > 0) {
|
|
259
272
|
currentMessages.push(...toolResults);
|
|
260
273
|
// Continue the loop to handle the next response
|
|
@@ -273,7 +286,6 @@ export async function streamChatGPT(backendUrl: string, messages: Message[], too
|
|
|
273
286
|
|
|
274
287
|
onResponse(messageId, content, true, toolInvocations);
|
|
275
288
|
return;
|
|
276
|
-
|
|
277
289
|
} catch (error) {
|
|
278
290
|
console.error('Error in streamChatGPT:', error);
|
|
279
291
|
onResponse(messageId, `Error: ${error instanceof Error ? error.message : String(error)}`, true, []);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { PluginController } from '../../plugin/PluginController';
|
|
3
|
+
|
|
4
|
+
export type TriggerAction = { action_key: string } & Record<string, string | number | boolean>;
|
|
5
|
+
|
|
6
|
+
export interface CreateExerciseParams {
|
|
7
|
+
plugin_id: string;
|
|
8
|
+
start_date: string;
|
|
9
|
+
end_date: string;
|
|
10
|
+
trigger_action: TriggerAction;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
estimated_duration: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Exercise {
|
|
17
|
+
id: string;
|
|
18
|
+
plugin_id: string;
|
|
19
|
+
start_date: string;
|
|
20
|
+
end_date: string;
|
|
21
|
+
trigger_action: TriggerAction;
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
estimated_duration: number;
|
|
25
|
+
created_at?: string;
|
|
26
|
+
updated_at?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ExerciseController {
|
|
30
|
+
private supabase: SupabaseClient;
|
|
31
|
+
private pluginController: PluginController;
|
|
32
|
+
|
|
33
|
+
constructor(supabase: SupabaseClient, pluginController: PluginController) {
|
|
34
|
+
this.supabase = supabase;
|
|
35
|
+
this.pluginController = pluginController;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetches weekly exercises from the weekly_exercises view.
|
|
40
|
+
* Shows exercises for the current week that haven't expired.
|
|
41
|
+
* @returns Array of exercise objects.
|
|
42
|
+
*/
|
|
43
|
+
public async viewWeeklyExercises(): Promise<Exercise[]> {
|
|
44
|
+
const { data, error } = await this.supabase.from('weekly_exercises').select('*');
|
|
45
|
+
|
|
46
|
+
if (error) {
|
|
47
|
+
throw new Error(`Failed to fetch weekly exercises: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return data || [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new exercise via the backend API.
|
|
55
|
+
* @param params Exercise creation parameters.
|
|
56
|
+
* @returns Created exercise object.
|
|
57
|
+
*/
|
|
58
|
+
public async addExercise(params: CreateExerciseParams): Promise<Exercise> {
|
|
59
|
+
const token = await this.pluginController.getToken();
|
|
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
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const errorText = await response.text();
|
|
71
|
+
throw new Error(`Failed to create exercise: ${errorText}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return await response.json();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Deletes an exercise via the backend API.
|
|
79
|
+
* @param id The exercise ID to delete.
|
|
80
|
+
* @returns Success status.
|
|
81
|
+
*/
|
|
82
|
+
public async deleteExercise(id: string): Promise<{ success: boolean; message: string }> {
|
|
83
|
+
const token = await this.pluginController.getToken();
|
|
84
|
+
const response = await fetch(`${this.pluginController.getBackendUrl()}/exercises/${id}`, {
|
|
85
|
+
method: 'DELETE',
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${token}`,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const errorText = await response.text();
|
|
93
|
+
throw new Error(`Failed to delete exercise: ${errorText}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return await response.json();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -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);
|
|
@@ -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
|
|
|
5
6
|
export interface Buddy {
|
|
6
7
|
id: string;
|
|
@@ -37,45 +38,117 @@ export interface UserInfo {
|
|
|
37
38
|
context_menu_on_select: boolean;
|
|
38
39
|
user_name?: string;
|
|
39
40
|
/**
|
|
40
|
-
* ISO 3166-1 alpha-2 country code of user's location (exposed to plugins)
|
|
41
|
+
* ISO 3166-1 alpha-2 country code of user's target location (exposed to plugins)
|
|
41
42
|
*/
|
|
42
|
-
|
|
43
|
+
target_country: string;
|
|
43
44
|
/**
|
|
44
45
|
* Optional: nearest big city (>100,000) near user's location
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
target_city?: string;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export class SettingsController {
|
|
50
51
|
private pluginId: string;
|
|
51
52
|
private supabase: SupabaseClient;
|
|
53
|
+
private guild: Guild;
|
|
52
54
|
|
|
53
|
-
constructor(supabase: SupabaseClient, pluginId: string) {
|
|
55
|
+
constructor(supabase: SupabaseClient, pluginId: string, guild: Guild) {
|
|
54
56
|
this.supabase = supabase;
|
|
55
57
|
this.pluginId = pluginId;
|
|
58
|
+
this.guild = guild;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Fetches settings based on guild configuration.
|
|
63
|
+
* If guild doesn't allow user settings, fetches guild-level settings.
|
|
64
|
+
* Otherwise, fetches user-specific settings.
|
|
65
|
+
* @returns The settings object or null if not found.
|
|
66
|
+
*/
|
|
58
67
|
private async fetchSettings(): Promise<any | null> {
|
|
59
|
-
const
|
|
68
|
+
const isGuildSetting = !this.guild.allowUserPluginSettings;
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
const { data } = await this.supabase
|
|
71
|
+
.from('plugin_settings')
|
|
72
|
+
.select('*')
|
|
73
|
+
.eq('plugin_id', this.pluginId)
|
|
74
|
+
.eq('guild_id', this.guild.id)
|
|
75
|
+
.eq('is_guild_setting', isGuildSetting)
|
|
76
|
+
.maybeSingle();
|
|
64
77
|
|
|
65
|
-
return data
|
|
78
|
+
return data?.settings ?? null;
|
|
66
79
|
}
|
|
67
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Sets settings for the plugin.
|
|
83
|
+
* Automatically saves as guild settings if guild doesn't allow user settings,
|
|
84
|
+
* otherwise saves as user-specific settings.
|
|
85
|
+
* @param settings - The settings object to save.
|
|
86
|
+
* @throws {Error} if RLS blocks the operation.
|
|
87
|
+
*/
|
|
68
88
|
public async setSettings(settings: any): Promise<void> {
|
|
69
|
-
|
|
89
|
+
const isGuildSetting = !this.guild.allowUserPluginSettings;
|
|
90
|
+
|
|
91
|
+
const payload: any = {
|
|
92
|
+
plugin_id: this.pluginId,
|
|
93
|
+
settings,
|
|
94
|
+
guild_id: this.guild.id,
|
|
95
|
+
is_guild_setting: isGuildSetting,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (isGuildSetting) {
|
|
99
|
+
payload.user_id = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Try UPDATE first (safe with RLS). If nothing updated, INSERT.
|
|
103
|
+
const updateQuery = this.supabase
|
|
104
|
+
.from('plugin_settings')
|
|
105
|
+
.update({ settings })
|
|
106
|
+
.eq('plugin_id', this.pluginId)
|
|
107
|
+
.eq('guild_id', this.guild.id)
|
|
108
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
109
|
+
|
|
110
|
+
const { data: updatedRows, error: updateError } = await (isGuildSetting
|
|
111
|
+
? updateQuery.is('user_id', null).select('id')
|
|
112
|
+
: updateQuery.select('id'));
|
|
113
|
+
|
|
114
|
+
if (updateError) {
|
|
115
|
+
if (updateError.code === '42501' || updateError.message?.includes('policy')) {
|
|
116
|
+
throw new Error(`Cannot set ${isGuildSetting ? 'guild' : 'user'} settings: Permission denied.`);
|
|
117
|
+
}
|
|
118
|
+
// proceed to try insert in case of other issues
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (updatedRows && updatedRows.length > 0) {
|
|
122
|
+
return; // updated successfully
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// No row updated -> INSERT
|
|
126
|
+
const { error: insertError } = await this.supabase.from('plugin_settings').insert(payload);
|
|
127
|
+
|
|
128
|
+
if (insertError) {
|
|
129
|
+
// In case of race condition (duplicate), try one more UPDATE
|
|
130
|
+
if (insertError.code === '23505' /* unique_violation */) {
|
|
131
|
+
const retry = this.supabase
|
|
132
|
+
.from('plugin_settings')
|
|
133
|
+
.update({ settings })
|
|
134
|
+
.eq('plugin_id', this.pluginId)
|
|
135
|
+
.eq('guild_id', this.guild.id)
|
|
136
|
+
.eq('is_guild_setting', isGuildSetting);
|
|
137
|
+
const { error: retryError } = await (isGuildSetting ? retry.is('user_id', null) : retry);
|
|
138
|
+
if (!retryError) return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw insertError;
|
|
142
|
+
}
|
|
70
143
|
}
|
|
71
144
|
|
|
72
145
|
/**
|
|
73
146
|
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
74
147
|
* @param defaultSettings The default settings to use if no settings are found.
|
|
75
|
-
* @returns The settings for the plugin.
|
|
148
|
+
* @returns The settings for the plugin.
|
|
76
149
|
*/
|
|
77
150
|
public async getSettings<T extends object>(defaultSettings: T): Promise<T> {
|
|
78
|
-
const storedSettings = await this.fetchSettings() as T | null;
|
|
151
|
+
const storedSettings = (await this.fetchSettings()) as T | null;
|
|
79
152
|
|
|
80
153
|
if (!storedSettings) {
|
|
81
154
|
await this.setSettings(defaultSettings);
|
|
@@ -88,7 +161,7 @@ export class SettingsController {
|
|
|
88
161
|
|
|
89
162
|
if (storedKeys.length !== defaultKeys.length) {
|
|
90
163
|
const validStoredSettings = Object.fromEntries(
|
|
91
|
-
Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key))
|
|
164
|
+
Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key)),
|
|
92
165
|
);
|
|
93
166
|
const mergedSettings = { ...defaultSettings, ...validStoredSettings } as T;
|
|
94
167
|
|