@rimori/client 1.2.0 → 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.
Files changed (75) hide show
  1. package/README.md +61 -18
  2. package/dist/cli/scripts/init/dev-registration.js +0 -1
  3. package/dist/cli/scripts/init/main.d.ts +1 -1
  4. package/dist/cli/scripts/init/main.js +1 -0
  5. package/dist/components/LoggerExample.d.ts +6 -0
  6. package/dist/components/LoggerExample.js +79 -0
  7. package/dist/components/ai/Assistant.js +2 -2
  8. package/dist/components/ai/Avatar.js +2 -2
  9. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -32
  10. package/dist/components/audio/Playbutton.js +2 -2
  11. package/dist/components/components/ContextMenu.js +48 -9
  12. package/dist/core/controller/AIController.js +202 -69
  13. package/dist/core/controller/AudioController.d.ts +0 -0
  14. package/dist/core/controller/AudioController.js +1 -0
  15. package/dist/core/controller/ObjectController.d.ts +2 -2
  16. package/dist/core/controller/ObjectController.js +8 -8
  17. package/dist/core/controller/SettingsController.d.ts +16 -0
  18. package/dist/core/controller/SharedContentController.d.ts +30 -2
  19. package/dist/core/controller/SharedContentController.js +74 -23
  20. package/dist/core/controller/VoiceController.d.ts +2 -3
  21. package/dist/core/controller/VoiceController.js +11 -4
  22. package/dist/core/core.d.ts +1 -0
  23. package/dist/fromRimori/EventBus.js +1 -1
  24. package/dist/fromRimori/PluginTypes.d.ts +7 -4
  25. package/dist/hooks/UseChatHook.js +6 -4
  26. package/dist/hooks/UseLogger.d.ts +30 -0
  27. package/dist/hooks/UseLogger.js +122 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/plugin/AudioController.d.ts +37 -0
  31. package/dist/plugin/AudioController.js +68 -0
  32. package/dist/plugin/Logger.d.ts +68 -0
  33. package/dist/plugin/Logger.js +256 -0
  34. package/dist/plugin/LoggerExample.d.ts +16 -0
  35. package/dist/plugin/LoggerExample.js +140 -0
  36. package/dist/plugin/PluginController.d.ts +15 -3
  37. package/dist/plugin/PluginController.js +162 -39
  38. package/dist/plugin/RimoriClient.d.ts +55 -13
  39. package/dist/plugin/RimoriClient.js +60 -23
  40. package/dist/plugin/StandaloneClient.d.ts +1 -0
  41. package/dist/plugin/StandaloneClient.js +16 -5
  42. package/dist/plugin/ThemeSetter.d.ts +2 -2
  43. package/dist/plugin/ThemeSetter.js +8 -5
  44. package/dist/providers/PluginProvider.d.ts +1 -1
  45. package/dist/providers/PluginProvider.js +36 -10
  46. package/dist/utils/audioFormats.d.ts +26 -0
  47. package/dist/utils/audioFormats.js +67 -0
  48. package/dist/worker/WorkerSetup.d.ts +3 -2
  49. package/dist/worker/WorkerSetup.js +22 -67
  50. package/package.json +2 -1
  51. package/src/cli/scripts/init/dev-registration.ts +0 -1
  52. package/src/cli/scripts/init/main.ts +1 -0
  53. package/src/components/ai/Assistant.tsx +2 -2
  54. package/src/components/ai/Avatar.tsx +2 -2
  55. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +39 -32
  56. package/src/components/audio/Playbutton.tsx +2 -2
  57. package/src/components/components/ContextMenu.tsx +53 -9
  58. package/src/core/controller/AIController.ts +236 -75
  59. package/src/core/controller/ObjectController.ts +8 -8
  60. package/src/core/controller/SettingsController.ts +16 -0
  61. package/src/core/controller/SharedContentController.ts +87 -25
  62. package/src/core/controller/VoiceController.ts +24 -19
  63. package/src/core/core.ts +1 -0
  64. package/src/fromRimori/EventBus.ts +1 -1
  65. package/src/fromRimori/PluginTypes.ts +6 -4
  66. package/src/hooks/UseChatHook.ts +6 -4
  67. package/src/index.ts +1 -0
  68. package/src/plugin/AudioController.ts +58 -0
  69. package/src/plugin/Logger.ts +324 -0
  70. package/src/plugin/PluginController.ts +171 -43
  71. package/src/plugin/RimoriClient.ts +95 -30
  72. package/src/plugin/StandaloneClient.ts +22 -6
  73. package/src/plugin/ThemeSetter.ts +8 -5
  74. package/src/providers/PluginProvider.tsx +40 -10
  75. package/src/worker/WorkerSetup.ts +14 -63
@@ -32,89 +32,222 @@ export function streamChatGPT(backendUrl, messages, tools, onResponse, token) {
32
32
  return __awaiter(this, void 0, void 0, function* () {
33
33
  const messageId = Math.random().toString(36).substring(3);
34
34
  let currentMessages = [...messages];
35
+ console.log('Starting streamChatGPT with:', {
36
+ messageId,
37
+ messageCount: messages.length,
38
+ toolCount: tools.length,
39
+ backendUrl
40
+ });
35
41
  while (true) {
36
42
  const messagesForApi = currentMessages.map((_a) => {
37
43
  var { id } = _a, rest = __rest(_a, ["id"]);
38
44
  return rest;
39
45
  });
40
- const response = yield fetch(`${backendUrl}/ai/llm`, {
41
- method: 'POST',
42
- body: JSON.stringify({ messages: messagesForApi, tools, stream: true }),
43
- headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
44
- });
45
- if (!response.body) {
46
- console.error('No response body.');
47
- return;
48
- }
49
- const reader = response.body.getReader();
50
- const decoder = new TextDecoder('utf-8');
51
- let content = "";
52
- let done = false;
53
- let toolInvocations = [];
54
- let finishReason = "";
55
- while (!done) {
56
- const { value, done: readerDone } = yield reader.read();
57
- if (value) {
58
- const chunk = decoder.decode(value, { stream: true });
59
- const lines = chunk.split('\n').filter(line => line.trim() !== '');
60
- for (const line of lines) {
61
- const command = line.substring(0, 1);
62
- if (command === '0') {
63
- const data = line.substring(3, line.length - 1);
64
- content += data;
65
- onResponse(messageId, content.replace(/\\n/g, '\n').replace(/\\+"/g, '"'), false);
66
- }
67
- else if (command === 'd' || command === 'e') {
68
- const eventData = JSON.parse(line.substring(2));
69
- finishReason = eventData.finishReason;
70
- done = true;
71
- break;
46
+ try {
47
+ const response = yield fetch(`${backendUrl}/ai/llm`, {
48
+ method: 'POST',
49
+ body: JSON.stringify({ messages: messagesForApi, tools, stream: true }),
50
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`HTTP error! status: ${response.status}`);
54
+ }
55
+ if (!response.body) {
56
+ console.error('No response body.');
57
+ return;
58
+ }
59
+ const reader = response.body.getReader();
60
+ const decoder = new TextDecoder('utf-8');
61
+ let content = "";
62
+ let done = false;
63
+ let toolInvocations = [];
64
+ let currentTextId = "";
65
+ let isToolCallMode = false;
66
+ let buffer = ""; // Buffer for incomplete chunks
67
+ while (!done) {
68
+ const { value, done: readerDone } = yield reader.read();
69
+ if (value) {
70
+ const chunk = decoder.decode(value, { stream: true });
71
+ buffer += chunk;
72
+ // Split by lines, but handle incomplete lines
73
+ const lines = buffer.split('\n');
74
+ // Keep the last line in buffer if it's incomplete
75
+ if (lines.length > 1) {
76
+ buffer = lines.pop() || "";
72
77
  }
73
- else if (command === '9') {
74
- const toolInvocation = JSON.parse(line.substring(2));
75
- toolInvocations.push(toolInvocation);
78
+ for (const line of lines) {
79
+ if (line.trim() === '')
80
+ continue;
81
+ // Handle the new streaming format
82
+ if (line.startsWith('data: ')) {
83
+ const dataStr = line.substring(6); // Remove 'data: ' prefix
84
+ // Handle [DONE] marker
85
+ if (dataStr === '[DONE]') {
86
+ done = true;
87
+ break;
88
+ }
89
+ try {
90
+ const data = JSON.parse(dataStr);
91
+ // Log the first message to understand the format
92
+ if (!content && !isToolCallMode) {
93
+ console.log('First stream message received:', data);
94
+ }
95
+ switch (data.type) {
96
+ case 'start':
97
+ // Stream started, no action needed
98
+ console.log('Stream started');
99
+ break;
100
+ case 'start-step':
101
+ // Step started, no action needed
102
+ console.log('Step started');
103
+ break;
104
+ case 'reasoning-start':
105
+ // Reasoning started, no action needed
106
+ console.log('Reasoning started:', data.id);
107
+ break;
108
+ case 'reasoning-end':
109
+ // Reasoning ended, no action needed
110
+ console.log('Reasoning ended:', data.id);
111
+ break;
112
+ case 'text-start':
113
+ // Text generation started, store the ID
114
+ currentTextId = data.id;
115
+ console.log('Text generation started:', data.id);
116
+ break;
117
+ case 'text-delta':
118
+ // Text delta received, append to content
119
+ if (data.delta) {
120
+ content += data.delta;
121
+ onResponse(messageId, content, false);
122
+ }
123
+ break;
124
+ case 'text-end':
125
+ // Text generation ended
126
+ console.log('Text generation ended:', data.id);
127
+ break;
128
+ case 'finish-step':
129
+ // Step finished, no action needed
130
+ console.log('Step finished');
131
+ break;
132
+ case 'finish':
133
+ // Stream finished
134
+ console.log('Stream finished');
135
+ done = true;
136
+ break;
137
+ // Additional message types that might be present in the AI library
138
+ case 'tool-call':
139
+ // Tool call initiated
140
+ console.log('Tool call initiated:', data);
141
+ isToolCallMode = true;
142
+ if (data.toolCallId && data.toolName && data.args) {
143
+ toolInvocations.push({
144
+ toolCallId: data.toolCallId,
145
+ toolName: data.toolName,
146
+ args: data.args
147
+ });
148
+ }
149
+ break;
150
+ case 'tool-call-delta':
151
+ // Tool call delta (for streaming tool calls)
152
+ console.log('Tool call delta:', data);
153
+ break;
154
+ case 'tool-call-end':
155
+ // Tool call completed
156
+ console.log('Tool call completed:', data);
157
+ break;
158
+ case 'tool-result':
159
+ // Tool execution result
160
+ console.log('Tool result:', data);
161
+ break;
162
+ case 'error':
163
+ // Error occurred
164
+ console.error('Stream error:', data);
165
+ break;
166
+ case 'usage':
167
+ // Usage information
168
+ console.log('Usage info:', data);
169
+ break;
170
+ case 'model':
171
+ // Model information
172
+ console.log('Model info:', data);
173
+ break;
174
+ case 'stop':
175
+ // Stop signal
176
+ console.log('Stop signal received');
177
+ done = true;
178
+ break;
179
+ default:
180
+ // Unknown type, log for debugging
181
+ console.log('Unknown stream type:', data.type, data);
182
+ break;
183
+ }
184
+ }
185
+ catch (error) {
186
+ console.error('Error parsing stream data:', error, dataStr);
187
+ }
188
+ }
76
189
  }
77
190
  }
191
+ if (readerDone) {
192
+ done = true;
193
+ }
78
194
  }
79
- if (readerDone) {
80
- done = true;
195
+ // Check if we have content or if this was a tool call response
196
+ if (content || toolInvocations.length > 0) {
197
+ currentMessages.push({
198
+ id: messageId,
199
+ role: "assistant",
200
+ content: content,
201
+ toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
202
+ });
81
203
  }
82
- }
83
- if (content || toolInvocations.length > 0) {
84
- currentMessages.push({
85
- id: messageId,
86
- role: "assistant",
87
- content: content,
88
- toolCalls: toolInvocations.length > 0 ? toolInvocations : undefined,
89
- });
90
- }
91
- if (finishReason !== 'tool-calls') {
92
- onResponse(messageId, content.replace(/\\n/g, '\n'), true, toolInvocations);
93
- return;
94
- }
95
- const toolResults = [];
96
- for (const toolInvocation of toolInvocations) {
97
- const tool = tools.find(t => t.name === toolInvocation.toolName);
98
- if (tool && tool.execute) {
99
- try {
100
- const result = yield tool.execute(toolInvocation.args);
101
- toolResults.push({
102
- id: Math.random().toString(36).substring(3),
103
- role: "user",
104
- content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
105
- });
204
+ // Handle tool call scenario if tools were provided
205
+ if (tools.length > 0 && toolInvocations.length > 0) {
206
+ console.log('Tool calls detected, executing tools...');
207
+ const toolResults = [];
208
+ for (const toolInvocation of toolInvocations) {
209
+ const tool = tools.find(t => t.name === toolInvocation.toolName);
210
+ if (tool && tool.execute) {
211
+ try {
212
+ const result = yield tool.execute(toolInvocation.args);
213
+ toolResults.push({
214
+ id: Math.random().toString(36).substring(3),
215
+ role: "user",
216
+ content: `Tool '${toolInvocation.toolName}' returned: ${JSON.stringify(result)}`,
217
+ });
218
+ }
219
+ catch (error) {
220
+ console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
221
+ toolResults.push({
222
+ id: Math.random().toString(36).substring(3),
223
+ role: "user",
224
+ content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
225
+ });
226
+ }
227
+ }
106
228
  }
107
- catch (error) {
108
- console.error(`Error executing tool ${toolInvocation.toolName}:`, error);
109
- toolResults.push({
110
- id: Math.random().toString(36).substring(3),
111
- role: "user",
112
- content: `Tool '${toolInvocation.toolName}' failed with error: ${error}`,
113
- });
229
+ if (toolResults.length > 0) {
230
+ currentMessages.push(...toolResults);
231
+ // Continue the loop to handle the next response
232
+ continue;
114
233
  }
115
234
  }
235
+ // Since the new format doesn't seem to support tool calls in the same way,
236
+ // we'll assume the stream is complete when we reach the end
237
+ // If tools are provided and no content was generated, this might indicate a tool call
238
+ if (tools.length > 0 && !content && !isToolCallMode) {
239
+ // This might be a tool call scenario, but we need more information
240
+ // For now, we'll just finish the stream
241
+ console.log('No content generated, but tools provided - might be tool call scenario');
242
+ }
243
+ onResponse(messageId, content, true, toolInvocations);
244
+ return;
245
+ }
246
+ catch (error) {
247
+ console.error('Error in streamChatGPT:', error);
248
+ onResponse(messageId, `Error: ${error instanceof Error ? error.message : String(error)}`, true, []);
249
+ return;
116
250
  }
117
- currentMessages.push(...toolResults);
118
251
  }
119
252
  });
120
253
  }
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -36,7 +36,7 @@ export interface ObjectRequest {
36
36
  */
37
37
  instructions: string;
38
38
  }
39
- export declare function generateObject(supabaseUrl: string, request: ObjectRequest, token: string): Promise<any>;
39
+ export declare function generateObject(backendUrl: string, request: ObjectRequest, token: string): Promise<any>;
40
40
  export type OnLLMResponse = (id: string, response: string, finished: boolean, toolInvocations?: any[]) => void;
41
- export declare function streamObject(supabaseUrl: string, request: ObjectRequest, onResponse: OnLLMResponse, token: string): Promise<void>;
41
+ export declare function streamObject(backendUrl: string, request: ObjectRequest, onResponse: OnLLMResponse, token: string): Promise<void>;
42
42
  export {};
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- export function generateObject(supabaseUrl, request, token) {
10
+ export function generateObject(backendUrl, request, token) {
11
11
  return __awaiter(this, void 0, void 0, function* () {
12
- return yield fetch(`${supabaseUrl}/functions/v1/llm-object`, {
12
+ return yield fetch(`${backendUrl}/ai/llm-object`, {
13
13
  method: 'POST',
14
14
  body: JSON.stringify({
15
15
  stream: false,
@@ -17,14 +17,14 @@ export function generateObject(supabaseUrl, request, token) {
17
17
  behaviour: request.behaviour,
18
18
  instructions: request.instructions,
19
19
  }),
20
- headers: { 'Authorization': `Bearer ${token}` }
20
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
21
21
  }).then(response => response.json());
22
22
  });
23
23
  }
24
- export function streamObject(supabaseUrl, request, onResponse, token) {
24
+ export function streamObject(backendUrl, request, onResponse, token) {
25
25
  return __awaiter(this, void 0, void 0, function* () {
26
26
  const messageId = Math.random().toString(36).substring(3);
27
- const response = yield fetch(`${supabaseUrl}/functions/v1/llm-object`, {
27
+ const response = yield fetch(`${backendUrl}/ai/llm-object`, {
28
28
  method: 'POST',
29
29
  body: JSON.stringify({
30
30
  stream: true,
@@ -32,7 +32,7 @@ export function streamObject(supabaseUrl, request, onResponse, token) {
32
32
  systemInstructions: request.behaviour,
33
33
  secondaryInstructions: request.instructions,
34
34
  }),
35
- headers: { 'Authorization': `Bearer ${token}` }
35
+ headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
36
36
  });
37
37
  if (!response.body) {
38
38
  console.error('No response body.');
@@ -56,7 +56,7 @@ export function streamObject(supabaseUrl, request, onResponse, token) {
56
56
  content += data;
57
57
  // console.log("AI response:", content);
58
58
  //content \n\n should be real line break when message is displayed
59
- onResponse(messageId, content.replace(/\\n/g, '\n'), false);
59
+ onResponse(messageId, content.replace(/\\n/g, '\n').replace(/\\+"/g, '"'), false);
60
60
  }
61
61
  else if (command === 'd') {
62
62
  // console.log("AI usage:", JSON.parse(line.substring(2)));
@@ -71,6 +71,6 @@ export function streamObject(supabaseUrl, request, onResponse, token) {
71
71
  }
72
72
  }
73
73
  }
74
- onResponse(messageId, content.replace(/\\n/g, '\n'), true, toolInvocations);
74
+ onResponse(messageId, content.replace(/\\n/g, '\n').replace(/\\+"/g, '"'), true, toolInvocations);
75
75
  });
76
76
  }
@@ -21,11 +21,27 @@ export interface UserInfo {
21
21
  study_buddy: Buddy;
22
22
  story_genre: string;
23
23
  study_duration: number;
24
+ /**
25
+ * The 2 letter language code of the language the user speaks natively.
26
+ * With the function getLanguageName, the language name can be retrieved.
27
+ */
24
28
  mother_tongue: Language;
29
+ /**
30
+ * The language the user targets to learn.
31
+ */
32
+ target_language: Language;
25
33
  motivation_type: string;
26
34
  onboarding_completed: boolean;
27
35
  context_menu_on_select: boolean;
28
36
  user_name?: string;
37
+ /**
38
+ * ISO 3166-1 alpha-2 country code of user's location (exposed to plugins)
39
+ */
40
+ location_country: string;
41
+ /**
42
+ * Optional: nearest big city (>100,000) near user's location
43
+ */
44
+ location_city?: string;
29
45
  }
30
46
  export declare class SettingsController {
31
47
  private pluginId;
@@ -14,14 +14,42 @@ export declare class SharedContentController {
14
14
  * @param contentType - The type of content to fetch.
15
15
  * @param generatorInstructions - The instructions for the generator. The object needs to have a tool property with a topic and keywords property to let a new unique topic be generated.
16
16
  * @param filter - An optional filter to apply to the query.
17
- * @param privateTopic - An optional flag to indicate if the topic should be private and only be visible to the user.
17
+ * @param options - Optional options.
18
+ * @param options.privateTopic - If the topic should be private and only be visible to the user.
19
+ * @param options.skipDbSave - If true, do not persist a newly generated content to the DB (default false).
20
+ * @param options.alwaysGenerateNew - If true, always generate a new content even if there is already a content with the same filter.
21
+ * @param options.excludeIds - Optional list of shared_content ids to exclude from selection.
18
22
  * @returns The new shared content.
19
23
  */
20
- getNewSharedContent<T>(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter, privateTopic?: boolean): Promise<SharedContent<T>>;
24
+ getNewSharedContent<T>(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter, options?: {
25
+ privateTopic?: boolean;
26
+ skipDbSave?: boolean;
27
+ alwaysGenerateNew?: boolean;
28
+ excludeIds?: string[];
29
+ }): Promise<SharedContent<T>>;
30
+ private generateNewAssignment;
21
31
  private getGeneratorInstructions;
22
32
  private getCompletedTopics;
23
33
  getSharedContent<T>(contentType: string, id: string): Promise<SharedContent<T>>;
24
34
  completeSharedContent(contentType: string, assignmentId: string): Promise<void>;
35
+ /**
36
+ * Update state details for a shared content entry in shared_content_completed.
37
+ * Assumes table has columns: state ('completed'|'ongoing'|'hidden'), reaction ('liked'|'disliked'|null), bookmarked boolean.
38
+ * Upserts per (id, content_type, user).
39
+ * @param param
40
+ * @param param.contentType - The content type.
41
+ * @param param.id - The shared content id.
42
+ * @param param.state - The state to set.
43
+ * @param param.reaction - Optional reaction.
44
+ * @param param.bookmarked - Optional bookmark flag.
45
+ */
46
+ updateSharedContentState({ contentType, id, state, reaction, bookmarked, }: {
47
+ contentType: string;
48
+ id: string;
49
+ state?: 'completed' | 'ongoing' | 'hidden';
50
+ reaction?: 'liked' | 'disliked' | null;
51
+ bookmarked?: boolean;
52
+ }): Promise<void>;
25
53
  /**
26
54
  * Fetch shared content from the database based on optional filters.
27
55
  * @param contentType - The type of content to fetch.
@@ -17,49 +17,63 @@ export class SharedContentController {
17
17
  * @param contentType - The type of content to fetch.
18
18
  * @param generatorInstructions - The instructions for the generator. The object needs to have a tool property with a topic and keywords property to let a new unique topic be generated.
19
19
  * @param filter - An optional filter to apply to the query.
20
- * @param privateTopic - An optional flag to indicate if the topic should be private and only be visible to the user.
20
+ * @param options - Optional options.
21
+ * @param options.privateTopic - If the topic should be private and only be visible to the user.
22
+ * @param options.skipDbSave - If true, do not persist a newly generated content to the DB (default false).
23
+ * @param options.alwaysGenerateNew - If true, always generate a new content even if there is already a content with the same filter.
24
+ * @param options.excludeIds - Optional list of shared_content ids to exclude from selection.
21
25
  * @returns The new shared content.
22
26
  */
23
27
  getNewSharedContent(contentType, generatorInstructions,
24
28
  //this filter is there if the content should be filtered additionally by a column and value
25
- filter, privateTopic) {
29
+ filter, options) {
26
30
  return __awaiter(this, void 0, void 0, function* () {
27
- const query = this.supabase.from("shared_content")
28
- .select("*, scc:shared_content_completed(id)")
31
+ let query = this.supabase.from("shared_content")
32
+ .select("*, scc:shared_content_completed(id, state)")
29
33
  .eq('content_type', contentType)
30
- .is('scc.id', null)
31
- .is('deleted_at', null)
32
- .limit(10);
34
+ .not('scc.state', 'in', '("completed","ongoing","hidden")')
35
+ .is('deleted_at', null);
36
+ if ((options === null || options === void 0 ? void 0 : options.excludeIds) && options.excludeIds.length > 0) {
37
+ const excludeIds = options.excludeIds.filter((id) => !id.startsWith('internal-temp-id-'));
38
+ // Supabase expects raw PostgREST syntax like '("id1","id2")'.
39
+ const excludeList = `(${excludeIds.map((id) => `"${id}"`).join(',')})`;
40
+ query = query.not('id', 'in', excludeList);
41
+ }
33
42
  if (filter) {
34
43
  query.contains('data', filter);
35
44
  }
36
- const { data: newAssignments, error } = yield query;
45
+ const { data: newAssignments, error } = yield query.limit(30);
37
46
  if (error) {
38
47
  console.error('error fetching new assignments:', error);
39
48
  throw new Error('error fetching new assignments');
40
49
  }
41
- console.log('newAssignments:', newAssignments);
42
- if (newAssignments.length > 0) {
50
+ // console.log('newAssignments:', newAssignments);
51
+ if (!(options === null || options === void 0 ? void 0 : options.alwaysGenerateNew) && newAssignments.length > 0) {
43
52
  const index = Math.floor(Math.random() * newAssignments.length);
44
53
  return newAssignments[index];
45
54
  }
46
- // generate new assignments
47
- const fullInstructions = yield this.getGeneratorInstructions(contentType, generatorInstructions, filter);
48
- console.log('fullInstructions:', fullInstructions);
49
- const instructions = yield this.rimoriClient.ai.getObject(fullInstructions);
55
+ const instructions = yield this.generateNewAssignment(contentType, generatorInstructions, filter);
50
56
  console.log('instructions:', instructions);
51
- const { data: newAssignment, error: insertError } = yield this.supabase.from("shared_content").insert({
52
- private: privateTopic,
53
- content_type: contentType,
57
+ //create the shared content object
58
+ const data = {
59
+ id: "internal-temp-id-" + Math.random().toString(36).substring(2, 15),
60
+ contentType,
54
61
  title: instructions.title,
55
62
  keywords: instructions.keywords.map(({ text }) => text),
56
63
  data: Object.assign(Object.assign(Object.assign({}, instructions), { title: undefined, keywords: undefined }), generatorInstructions.fixedProperties),
57
- }).select();
58
- if (insertError) {
59
- console.error('error inserting new assignment:', insertError);
60
- throw new Error('error inserting new assignment');
64
+ privateTopic: options === null || options === void 0 ? void 0 : options.privateTopic,
65
+ };
66
+ if (options === null || options === void 0 ? void 0 : options.skipDbSave) {
67
+ return data;
61
68
  }
62
- return newAssignment[0];
69
+ return yield this.createSharedContent(data);
70
+ });
71
+ }
72
+ generateNewAssignment(contentType, generatorInstructions, filter) {
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ const fullInstructions = yield this.getGeneratorInstructions(contentType, generatorInstructions, filter);
75
+ console.log('fullInstructions:', fullInstructions);
76
+ return yield this.rimoriClient.ai.getObject(fullInstructions);
63
77
  });
64
78
  }
65
79
  getGeneratorInstructions(contentType, generatorInstructions, filter) {
@@ -108,7 +122,44 @@ export class SharedContentController {
108
122
  }
109
123
  completeSharedContent(contentType, assignmentId) {
110
124
  return __awaiter(this, void 0, void 0, function* () {
111
- yield this.supabase.from("shared_content_completed").insert({ content_type: contentType, id: assignmentId });
125
+ // Idempotent completion: upsert on (id, user_id) so repeated calls don't fail
126
+ const { error } = yield this.supabase
127
+ .from("shared_content_completed")
128
+ .upsert({ content_type: contentType, id: assignmentId }, { onConflict: 'id' });
129
+ if (error) {
130
+ console.error('error completing shared content:', error);
131
+ throw new Error('error completing shared content');
132
+ }
133
+ });
134
+ }
135
+ /**
136
+ * Update state details for a shared content entry in shared_content_completed.
137
+ * Assumes table has columns: state ('completed'|'ongoing'|'hidden'), reaction ('liked'|'disliked'|null), bookmarked boolean.
138
+ * Upserts per (id, content_type, user).
139
+ * @param param
140
+ * @param param.contentType - The content type.
141
+ * @param param.id - The shared content id.
142
+ * @param param.state - The state to set.
143
+ * @param param.reaction - Optional reaction.
144
+ * @param param.bookmarked - Optional bookmark flag.
145
+ */
146
+ updateSharedContentState(_a) {
147
+ return __awaiter(this, arguments, void 0, function* ({ contentType, id, state, reaction, bookmarked, }) {
148
+ const payload = { content_type: contentType, id };
149
+ if (state !== undefined)
150
+ payload.state = state;
151
+ if (reaction !== undefined)
152
+ payload.reaction = reaction;
153
+ if (bookmarked !== undefined)
154
+ payload.bookmarked = bookmarked;
155
+ // 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' });
159
+ if (error) {
160
+ console.error('error updating shared content state:', error);
161
+ throw new Error('error updating shared content state');
162
+ }
112
163
  });
113
164
  }
114
165
  /**
@@ -1,6 +1,5 @@
1
- import { SupabaseClient } from "@supabase/supabase-js";
2
- export declare function getSTTResponse(supabase: SupabaseClient, audio: Blob): Promise<any>;
3
- export declare function getTTSResponse(supabaseUrl: string, request: TTSRequest, token: string): Promise<Blob>;
1
+ export declare function getSTTResponse(backendUrl: string, audio: Blob, token: string): Promise<any>;
2
+ export declare function getTTSResponse(backendUrl: string, request: TTSRequest, token: string): Promise<Blob>;
4
3
  interface TTSRequest {
5
4
  input: string;
6
5
  voice: string;
@@ -7,16 +7,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- export function getSTTResponse(supabase, audio) {
10
+ export function getSTTResponse(backendUrl, audio, token) {
11
11
  return __awaiter(this, void 0, void 0, function* () {
12
12
  const formData = new FormData();
13
13
  formData.append('file', audio);
14
- return yield supabase.functions.invoke('speech', { method: 'POST', body: formData }).then(({ data }) => data.text);
14
+ return yield fetch(`${backendUrl}/voice/stt`, {
15
+ method: 'POST',
16
+ headers: { 'Authorization': `Bearer ${token}` },
17
+ body: formData,
18
+ }).then(r => r.json()).then(r => {
19
+ // console.log("STT response: ", r);
20
+ return r.text;
21
+ });
15
22
  });
16
23
  }
17
- export function getTTSResponse(supabaseUrl, request, token) {
24
+ export function getTTSResponse(backendUrl, request, token) {
18
25
  return __awaiter(this, void 0, void 0, function* () {
19
- return yield fetch(`${supabaseUrl}/functions/v1/speech`, {
26
+ return yield fetch(`${backendUrl}/voice/tts`, {
20
27
  method: 'POST',
21
28
  headers: {
22
29
  'Content-Type': 'application/json',
@@ -11,3 +11,4 @@ export { SharedContent } from "./controller/SharedContentController";
11
11
  export { Message, OnLLMResponse, ToolInvocation } from "./controller/AIController";
12
12
  export { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from "../plugin/AccomplishmentHandler";
13
13
  export { Tool } from "../fromRimori/PluginTypes";
14
+ export { SharedContentObjectRequest } from "./controller/SharedContentController";
@@ -11,7 +11,7 @@ export class EventBusHandler {
11
11
  constructor() {
12
12
  this.listeners = new Map();
13
13
  this.responseResolvers = new Map();
14
- this.debugEnabled = true;
14
+ this.debugEnabled = false;
15
15
  this.evName = "";
16
16
  //private constructor
17
17
  }