@rimori/client 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/components/MarkdownEditor.js +6 -4
- package/dist/components/PluginController.d.ts +21 -0
- package/dist/components/PluginController.js +116 -0
- package/dist/components/ai/Assistant.js +1 -1
- package/dist/components/ai/Avatar.d.ts +5 -3
- package/dist/components/ai/Avatar.js +14 -6
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +2 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +35 -14
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +3 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +2 -0
- package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +5 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +3 -0
- package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -5
- package/dist/components/ai/utils.d.ts +1 -1
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/controller/AIController.js +2 -1
- package/dist/controller/SettingsController.d.ts +18 -10
- package/dist/controller/SettingsController.js +28 -31
- package/dist/controller/SharedContentController.d.ts +58 -11
- package/dist/controller/SharedContentController.js +161 -26
- package/dist/controller/SidePluginController.d.ts +1 -12
- package/dist/core/components/ContextMenu.d.ts +10 -0
- package/dist/core/components/ContextMenu.js +93 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +2 -0
- package/dist/hooks/UseChatHook.d.ts +1 -1
- package/dist/plugin/AccomplishmentHandler.d.ts +38 -0
- package/dist/plugin/AccomplishmentHandler.js +108 -0
- package/dist/plugin/ContextMenu.d.ts +17 -0
- package/dist/plugin/ContextMenu.js +45 -0
- package/dist/plugin/PluginController.js +9 -4
- package/dist/plugin/RimoriClient.d.ts +92 -65
- package/dist/plugin/RimoriClient.js +105 -75
- package/dist/plugin/ThemeSetter.js +4 -4
- package/dist/plugin/fromRimori/EventBus.d.ts +6 -3
- package/dist/plugin/fromRimori/EventBus.js +15 -9
- package/dist/plugin/fromRimori/PluginTypes.d.ts +51 -0
- package/dist/plugin/fromRimori/PluginTypes.js +1 -0
- package/dist/providers/PluginController.d.ts +21 -0
- package/dist/providers/PluginController.js +116 -0
- package/dist/providers/PluginProvider.js +26 -73
- package/dist/types/Actions.d.ts +4 -0
- package/dist/types/Actions.js +1 -0
- package/dist/utils/Language.d.ts +66 -0
- package/dist/utils/Language.js +67 -0
- package/dist/utils/difficultyConverter.d.ts +1 -0
- package/dist/utils/difficultyConverter.js +3 -0
- package/dist/worker/WorkerSetup.js +5 -4
- package/package.json +3 -3
- package/src/components/MarkdownEditor.tsx +78 -76
- package/src/components/ai/Assistant.tsx +1 -1
- package/src/components/ai/Avatar.tsx +65 -48
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +81 -58
- package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +4 -0
- package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +6 -0
- package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +51 -8
- package/src/components/ai/utils.ts +1 -1
- package/src/components.ts +2 -1
- package/src/controller/AIController.ts +2 -1
- package/src/controller/SettingsController.ts +80 -75
- package/src/controller/SharedContentController.ts +214 -53
- package/src/controller/SidePluginController.ts +1 -13
- package/src/core/components/ContextMenu.tsx +123 -0
- package/src/core.ts +3 -1
- package/src/hooks/UseChatHook.ts +17 -17
- package/src/plugin/AccomplishmentHandler.ts +165 -0
- package/src/plugin/PluginController.ts +107 -100
- package/src/plugin/RimoriClient.ts +267 -250
- package/src/plugin/ThemeSetter.ts +4 -5
- package/src/plugin/fromRimori/EventBus.ts +23 -12
- package/src/plugin/fromRimori/PluginTypes.ts +67 -0
- package/src/providers/PluginProvider.tsx +63 -110
- package/src/types/Actions.ts +6 -0
- package/src/utils/Language.ts +70 -0
- package/src/utils/difficultyConverter.ts +4 -0
- package/src/worker/WorkerSetup.ts +5 -4
- package/dist/components/avatar/Assistant.d.ts +0 -9
- package/dist/components/avatar/Assistant.js +0 -59
- package/dist/components/avatar/Avatar.d.ts +0 -12
- package/dist/components/avatar/Avatar.js +0 -42
- package/dist/components/avatar/EmbeddedAssistent/AudioInputField.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/AudioInputField.js +0 -38
- package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.js +0 -59
- package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
- package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.js +0 -84
- package/dist/components/avatar/EmbeddedAssistent/TTS/Player.d.ts +0 -25
- package/dist/components/avatar/EmbeddedAssistent/TTS/Player.js +0 -180
- package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.d.ts +0 -7
- package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.js +0 -45
- package/dist/components/avatar/utils.d.ts +0 -6
- package/dist/components/avatar/utils.js +0 -14
|
@@ -12,50 +12,55 @@ export class SettingsController {
|
|
|
12
12
|
this.supabase = supabase;
|
|
13
13
|
this.pluginId = pluginId;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
return genericSettings || "plugin";
|
|
17
|
-
}
|
|
18
|
-
fetchSettings(type) {
|
|
15
|
+
fetchSettings() {
|
|
19
16
|
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
-
const
|
|
21
|
-
const { data } = yield this.supabase.from("plugin_settings").select("*").eq("plugin_id", pluginId);
|
|
17
|
+
const { data } = yield this.supabase.from("plugin_settings").select("*").eq("plugin_id", this.pluginId);
|
|
22
18
|
if (!data || data.length === 0) {
|
|
23
19
|
return null;
|
|
24
20
|
}
|
|
25
21
|
return data[0].settings;
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
|
-
|
|
24
|
+
setSettings(settings) {
|
|
29
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
if (type !== "plugin") {
|
|
31
|
-
throw new Error(`Cannot modify ${type} settings`);
|
|
32
|
-
}
|
|
33
26
|
yield this.supabase.from("plugin_settings").upsert({ plugin_id: this.pluginId, settings });
|
|
34
27
|
});
|
|
35
28
|
}
|
|
36
29
|
getUserInfo() {
|
|
37
30
|
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
const { data } = yield this.supabase.from("profiles").select("*");
|
|
32
|
+
if (!data || data.length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
mother_tongue: "en",
|
|
35
|
+
skill_level_listening: "Pre-A1",
|
|
36
|
+
skill_level_reading: "Pre-A1",
|
|
37
|
+
skill_level_speaking: "Pre-A1",
|
|
38
|
+
skill_level_writing: "Pre-A1",
|
|
39
|
+
skill_level_understanding: "Pre-A1",
|
|
40
|
+
skill_level_grammar: "Pre-A1",
|
|
41
|
+
goal_longterm: "",
|
|
42
|
+
goal_weekly: "",
|
|
43
|
+
study_buddy: "clarence",
|
|
44
|
+
story_genre: "adventure",
|
|
45
|
+
study_duration: 30,
|
|
46
|
+
motivation_type: "self-motivated",
|
|
47
|
+
onboarding_completed: false,
|
|
48
|
+
context_menu_on_select: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return data[0].settings;
|
|
43
52
|
});
|
|
44
53
|
}
|
|
45
54
|
/**
|
|
46
55
|
* Get the settings for the plugin. T can be any type of settings, UserSettings or SystemSettings.
|
|
47
56
|
* @param defaultSettings The default settings to use if no settings are found.
|
|
48
|
-
* @param genericSettings The type of settings to get.
|
|
49
57
|
* @returns The settings for the plugin.
|
|
50
58
|
*/
|
|
51
|
-
getSettings(defaultSettings
|
|
59
|
+
getSettings(defaultSettings) {
|
|
52
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
-
const
|
|
54
|
-
const storedSettings = yield this.fetchSettings(type);
|
|
61
|
+
const storedSettings = yield this.fetchSettings();
|
|
55
62
|
if (!storedSettings) {
|
|
56
|
-
|
|
57
|
-
yield this.saveSettings(defaultSettings, type);
|
|
58
|
-
}
|
|
63
|
+
yield this.setSettings(defaultSettings);
|
|
59
64
|
return defaultSettings;
|
|
60
65
|
}
|
|
61
66
|
// Handle settings migration
|
|
@@ -64,18 +69,10 @@ export class SettingsController {
|
|
|
64
69
|
if (storedKeys.length !== defaultKeys.length) {
|
|
65
70
|
const validStoredSettings = Object.fromEntries(Object.entries(storedSettings).filter(([key]) => defaultKeys.includes(key)));
|
|
66
71
|
const mergedSettings = Object.assign(Object.assign({}, defaultSettings), validStoredSettings);
|
|
67
|
-
|
|
68
|
-
yield this.saveSettings(mergedSettings, type);
|
|
69
|
-
}
|
|
72
|
+
yield this.setSettings(mergedSettings);
|
|
70
73
|
return mergedSettings;
|
|
71
74
|
}
|
|
72
75
|
return storedSettings;
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
|
-
setSettings(settings, genericSettings) {
|
|
76
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
-
const type = this.getSettingsType(genericSettings);
|
|
78
|
-
yield this.saveSettings(settings, type);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
78
|
}
|
|
@@ -1,22 +1,69 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
2
|
import { RimoriClient } from "../plugin/RimoriClient";
|
|
3
|
-
|
|
3
|
+
import { ObjectRequest } from "./ObjectController";
|
|
4
|
+
export interface BasicAssignment<T> {
|
|
4
5
|
id: string;
|
|
5
6
|
createdAt: Date;
|
|
6
7
|
topic: string;
|
|
7
8
|
createdBy: string;
|
|
8
9
|
verified: boolean;
|
|
9
10
|
keywords: any;
|
|
11
|
+
data: T;
|
|
12
|
+
}
|
|
13
|
+
export interface SharedContentObjectRequest extends ObjectRequest {
|
|
14
|
+
fixedProperties?: Record<string, string | number | boolean>;
|
|
10
15
|
}
|
|
16
|
+
export type SharedContentFilter = Record<string, string | number | boolean>;
|
|
11
17
|
export declare class SharedContentController {
|
|
18
|
+
private supabase;
|
|
12
19
|
private rimoriClient;
|
|
13
|
-
constructor(rimoriClient: RimoriClient);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
constructor(supabase: SupabaseClient, rimoriClient: RimoriClient);
|
|
21
|
+
/**
|
|
22
|
+
* Fetch new shared content for a given content type.
|
|
23
|
+
* @param contentType - The type of content to fetch.
|
|
24
|
+
* @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.
|
|
25
|
+
* @param filter - An optional filter to apply to the query.
|
|
26
|
+
* @param privateTopic - An optional flag to indicate if the topic should be private and only be visible to the user.
|
|
27
|
+
* @returns The new shared content.
|
|
28
|
+
*/
|
|
29
|
+
getNewSharedContent<T>(contentType: string, generatorInstructions: SharedContentObjectRequest, filter?: SharedContentFilter, privateTopic?: boolean): Promise<BasicAssignment<T>>;
|
|
30
|
+
private getGeneratorInstructions;
|
|
31
|
+
private getCompletedTopics;
|
|
32
|
+
getSharedContent<T>(contentType: string, id: string): Promise<BasicAssignment<T>>;
|
|
33
|
+
completeSharedContent(contentType: string, assignmentId: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Fetch shared content from the database based on optional filters.
|
|
36
|
+
* @param contentType - The type of content to fetch.
|
|
37
|
+
* @param filter - Optional filter to apply to the query.
|
|
38
|
+
* @param limit - Optional limit for the number of results.
|
|
39
|
+
* @returns Array of shared content matching the criteria.
|
|
40
|
+
*/
|
|
41
|
+
getSharedContentList<T>(contentType: string, filter?: SharedContentFilter, limit?: number): Promise<BasicAssignment<T>[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Insert new shared content into the database.
|
|
44
|
+
* @param param
|
|
45
|
+
* @param param.contentType - The type of content to insert.
|
|
46
|
+
* @param param.topic - The topic of the content.
|
|
47
|
+
* @param param.keywords - Keywords associated with the content.
|
|
48
|
+
* @param param.data - The content data to store.
|
|
49
|
+
* @param param.privateTopic - Optional flag to indicate if the topic should be private.
|
|
50
|
+
* @returns The inserted shared content.
|
|
51
|
+
* @throws {Error} if insertion fails.
|
|
52
|
+
*/
|
|
53
|
+
createSharedContent<T>({ contentType, topic, keywords, data, privateTopic }: SharedContent<T>): Promise<BasicAssignment<T>>;
|
|
54
|
+
/**
|
|
55
|
+
* Update existing shared content in the database.
|
|
56
|
+
* @param id - The ID of the content to update.
|
|
57
|
+
* @param updates - The updates to apply to the shared content.
|
|
58
|
+
* @returns The updated shared content.
|
|
59
|
+
* @throws {Error} if update fails.
|
|
60
|
+
*/
|
|
61
|
+
updateSharedContent<T>(id: string, updates: Partial<SharedContent<T>>): Promise<BasicAssignment<T>>;
|
|
62
|
+
}
|
|
63
|
+
export interface SharedContent<T> {
|
|
64
|
+
contentType: string;
|
|
65
|
+
topic: string;
|
|
66
|
+
keywords: string[];
|
|
67
|
+
data: T;
|
|
68
|
+
privateTopic?: boolean;
|
|
22
69
|
}
|
|
@@ -7,50 +7,185 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
11
10
|
export class SharedContentController {
|
|
12
|
-
constructor(rimoriClient) {
|
|
11
|
+
constructor(supabase, rimoriClient) {
|
|
12
|
+
this.supabase = supabase;
|
|
13
13
|
this.rimoriClient = rimoriClient;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Fetch new shared content for a given content type.
|
|
17
|
+
* @param contentType - The type of content to fetch.
|
|
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
|
+
* @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.
|
|
21
|
+
* @returns The new shared content.
|
|
22
|
+
*/
|
|
23
|
+
getNewSharedContent(contentType, generatorInstructions,
|
|
24
|
+
//this filter is there if the content should be filtered additionally by a column and value
|
|
25
|
+
filter, privateTopic) {
|
|
16
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
const
|
|
18
|
-
|
|
27
|
+
const query = this.supabase.from("shared_content")
|
|
28
|
+
.select("*, scc:shared_content_completed(id)")
|
|
29
|
+
.eq('content_type', contentType)
|
|
30
|
+
.is('scc.id', null)
|
|
31
|
+
.limit(10);
|
|
32
|
+
if (filter) {
|
|
33
|
+
query.contains('data', filter);
|
|
34
|
+
}
|
|
35
|
+
const { data: newAssignments, error } = yield query;
|
|
36
|
+
if (error) {
|
|
37
|
+
console.error('error fetching new assignments:', error);
|
|
38
|
+
throw new Error('error fetching new assignments');
|
|
39
|
+
}
|
|
19
40
|
console.log('newAssignments:', newAssignments);
|
|
20
41
|
if (newAssignments.length > 0) {
|
|
21
|
-
|
|
42
|
+
const index = Math.floor(Math.random() * newAssignments.length);
|
|
43
|
+
return newAssignments[index];
|
|
22
44
|
}
|
|
23
45
|
// generate new assignments
|
|
24
|
-
const
|
|
25
|
-
console.log('
|
|
26
|
-
const
|
|
27
|
-
const request = yield generatorInstructions(reservedTopics);
|
|
28
|
-
if (!request.tool.keywords || !request.tool.topic) {
|
|
29
|
-
throw new Error("topic or keywords not found in the request schema");
|
|
30
|
-
}
|
|
31
|
-
const instructions = yield this.rimoriClient.llm.getObject(request);
|
|
46
|
+
const fullInstructions = yield this.getGeneratorInstructions(contentType, generatorInstructions, filter);
|
|
47
|
+
console.log('fullInstructions:', fullInstructions);
|
|
48
|
+
const instructions = yield this.rimoriClient.llm.getObject(fullInstructions);
|
|
32
49
|
console.log('instructions:', instructions);
|
|
33
|
-
const
|
|
34
|
-
|
|
50
|
+
const { data: newAssignment, error: insertError } = yield this.supabase.from("shared_content").insert({
|
|
51
|
+
private: privateTopic,
|
|
52
|
+
content_type: contentType,
|
|
53
|
+
topic: instructions.topic,
|
|
54
|
+
keywords: instructions.keywords.map(({ text }) => text),
|
|
55
|
+
data: Object.assign(Object.assign(Object.assign({}, instructions), { topic: undefined, keywords: undefined }), generatorInstructions.fixedProperties),
|
|
56
|
+
}).select();
|
|
57
|
+
if (insertError) {
|
|
58
|
+
console.error('error inserting new assignment:', insertError);
|
|
59
|
+
throw new Error('error inserting new assignment');
|
|
60
|
+
}
|
|
61
|
+
return newAssignment[0];
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
getGeneratorInstructions(contentType, generatorInstructions, filter) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const completedTopics = yield this.getCompletedTopics(contentType, filter);
|
|
67
|
+
generatorInstructions.instructions += `
|
|
68
|
+
The following topics are already taken: ${completedTopics.join(', ')}`;
|
|
69
|
+
generatorInstructions.tool.topic = {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "What the topic is about. Short. ",
|
|
72
|
+
};
|
|
73
|
+
generatorInstructions.tool.keywords = {
|
|
74
|
+
type: [{ text: { type: "string" } }],
|
|
75
|
+
description: "Keywords around the topic of the assignment.",
|
|
76
|
+
};
|
|
77
|
+
return generatorInstructions;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
getCompletedTopics(contentType, filter) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
const query = this.supabase.from("shared_content")
|
|
83
|
+
.select("topic, keywords, scc:shared_content_completed(id)")
|
|
84
|
+
.eq('content_type', contentType)
|
|
85
|
+
.not('scc.id', 'is', null);
|
|
86
|
+
if (filter) {
|
|
87
|
+
query.contains('data', filter);
|
|
88
|
+
}
|
|
89
|
+
const { data: oldAssignments, error } = yield query;
|
|
90
|
+
if (error) {
|
|
91
|
+
console.error('error fetching old assignments:', error);
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
return oldAssignments.map(({ topic, keywords }) => `${topic}(${keywords.join(',')})`);
|
|
35
95
|
});
|
|
36
96
|
}
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
const
|
|
40
|
-
|
|
97
|
+
getSharedContent(contentType, id) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const { data, error } = yield this.supabase.from("shared_content").select().eq('content_type', contentType).eq('id', id).single();
|
|
100
|
+
if (error) {
|
|
101
|
+
console.error('error fetching shared content:', error);
|
|
102
|
+
throw new Error('error fetching shared content');
|
|
103
|
+
}
|
|
104
|
+
return data;
|
|
41
105
|
});
|
|
42
106
|
}
|
|
43
|
-
|
|
44
|
-
return
|
|
107
|
+
completeSharedContent(contentType, assignmentId) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
yield this.supabase.from("shared_content_completed").insert({ content_type: contentType, id: assignmentId });
|
|
110
|
+
});
|
|
45
111
|
}
|
|
46
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Fetch shared content from the database based on optional filters.
|
|
114
|
+
* @param contentType - The type of content to fetch.
|
|
115
|
+
* @param filter - Optional filter to apply to the query.
|
|
116
|
+
* @param limit - Optional limit for the number of results.
|
|
117
|
+
* @returns Array of shared content matching the criteria.
|
|
118
|
+
*/
|
|
119
|
+
getSharedContentList(contentType, filter, limit) {
|
|
47
120
|
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
-
|
|
121
|
+
const query = this.supabase.from("shared_content").select("*").eq('content_type', contentType).limit(limit !== null && limit !== void 0 ? limit : 30);
|
|
122
|
+
if (filter) {
|
|
123
|
+
query.contains('data', filter);
|
|
124
|
+
}
|
|
125
|
+
const { data, error } = yield query;
|
|
126
|
+
if (error) {
|
|
127
|
+
console.error('error fetching shared content:', error);
|
|
128
|
+
throw new Error('error fetching shared content');
|
|
129
|
+
}
|
|
130
|
+
return data;
|
|
49
131
|
});
|
|
50
132
|
}
|
|
51
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Insert new shared content into the database.
|
|
135
|
+
* @param param
|
|
136
|
+
* @param param.contentType - The type of content to insert.
|
|
137
|
+
* @param param.topic - The topic of the content.
|
|
138
|
+
* @param param.keywords - Keywords associated with the content.
|
|
139
|
+
* @param param.data - The content data to store.
|
|
140
|
+
* @param param.privateTopic - Optional flag to indicate if the topic should be private.
|
|
141
|
+
* @returns The inserted shared content.
|
|
142
|
+
* @throws {Error} if insertion fails.
|
|
143
|
+
*/
|
|
144
|
+
createSharedContent(_a) {
|
|
145
|
+
return __awaiter(this, arguments, void 0, function* ({ contentType, topic, keywords, data, privateTopic }) {
|
|
146
|
+
const { data: newContent, error } = yield this.supabase.from("shared_content").insert({
|
|
147
|
+
private: privateTopic,
|
|
148
|
+
content_type: contentType,
|
|
149
|
+
topic,
|
|
150
|
+
keywords,
|
|
151
|
+
data,
|
|
152
|
+
}).select();
|
|
153
|
+
if (error) {
|
|
154
|
+
console.error('error inserting shared content:', error);
|
|
155
|
+
throw new Error('error inserting shared content');
|
|
156
|
+
}
|
|
157
|
+
return newContent[0];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Update existing shared content in the database.
|
|
162
|
+
* @param id - The ID of the content to update.
|
|
163
|
+
* @param updates - The updates to apply to the shared content.
|
|
164
|
+
* @returns The updated shared content.
|
|
165
|
+
* @throws {Error} if update fails.
|
|
166
|
+
*/
|
|
167
|
+
updateSharedContent(id, updates) {
|
|
52
168
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
-
|
|
169
|
+
const updateData = {};
|
|
170
|
+
if (updates.contentType)
|
|
171
|
+
updateData.content_type = updates.contentType;
|
|
172
|
+
if (updates.topic)
|
|
173
|
+
updateData.topic = updates.topic;
|
|
174
|
+
if (updates.keywords)
|
|
175
|
+
updateData.keywords = updates.keywords;
|
|
176
|
+
if (updates.data)
|
|
177
|
+
updateData.data = updates.data;
|
|
178
|
+
if (updates.privateTopic !== undefined)
|
|
179
|
+
updateData.private = updates.privateTopic;
|
|
180
|
+
const { data: updatedContent, error } = yield this.supabase.from("shared_content").update(updateData).eq('id', id).select();
|
|
181
|
+
if (error) {
|
|
182
|
+
console.error('error updating shared content:', error);
|
|
183
|
+
throw new Error('error updating shared content');
|
|
184
|
+
}
|
|
185
|
+
if (!updatedContent || updatedContent.length === 0) {
|
|
186
|
+
throw new Error('shared content not found');
|
|
187
|
+
}
|
|
188
|
+
return updatedContent[0];
|
|
54
189
|
});
|
|
55
190
|
}
|
|
56
191
|
}
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
|
|
3
|
-
id: string;
|
|
4
|
-
title: string;
|
|
5
|
-
icon_url: string;
|
|
6
|
-
website: string;
|
|
7
|
-
context_menu_actions: string;
|
|
8
|
-
plugin_pages: string;
|
|
9
|
-
sidebar_pages: string;
|
|
10
|
-
settings_page: string;
|
|
11
|
-
version: string;
|
|
12
|
-
external_hosted_url: string;
|
|
13
|
-
}
|
|
2
|
+
import { Plugin } from '../plugin/fromRimori/PluginTypes';
|
|
14
3
|
export declare function getPlugins(supabase: SupabaseClient): Promise<Plugin[]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RimoriClient } from "../../plugin/RimoriClient";
|
|
2
|
+
export interface Position {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
text?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const ContextMenu: ({ client }: {
|
|
8
|
+
client: RimoriClient;
|
|
9
|
+
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useRef } from "react";
|
|
3
|
+
import { EventBus } from "../../plugin/fromRimori/EventBus";
|
|
4
|
+
const ContextMenu = ({ client }) => {
|
|
5
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
6
|
+
const [actions, setActions] = useState([]);
|
|
7
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
8
|
+
const [openOnTextSelect, setOpenOnTextSelect] = useState(false);
|
|
9
|
+
const menuRef = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
client.plugin.getInstalled().then(plugins => {
|
|
12
|
+
setActions(plugins.flatMap(p => p.context_menu_actions).filter(Boolean));
|
|
13
|
+
});
|
|
14
|
+
client.plugin.getUserInfo().then((userInfo) => {
|
|
15
|
+
setOpenOnTextSelect(userInfo.context_menu_on_select);
|
|
16
|
+
});
|
|
17
|
+
EventBus.on("global.contextMenu.createActions", ({ data }) => {
|
|
18
|
+
setActions([...data.actions, ...actions]);
|
|
19
|
+
});
|
|
20
|
+
}, []);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Track mouse position globally
|
|
23
|
+
const handleMouseMove = (e) => {
|
|
24
|
+
var _a;
|
|
25
|
+
const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
|
|
26
|
+
if (isOpen && selectedText === position.text)
|
|
27
|
+
return;
|
|
28
|
+
setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
|
|
29
|
+
};
|
|
30
|
+
const handleMouseUp = (e) => {
|
|
31
|
+
var _a, _b;
|
|
32
|
+
const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
|
|
33
|
+
// Check if click is inside the context menu
|
|
34
|
+
if (menuRef.current && menuRef.current.contains(e.target)) {
|
|
35
|
+
// Don't close the menu if clicking inside
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Prevent context menu on textarea or text input selection
|
|
39
|
+
const target = e.target;
|
|
40
|
+
const isTextInput = target && ((target.tagName === 'TEXTAREA') ||
|
|
41
|
+
(target.tagName === 'INPUT' && target.type === 'text'));
|
|
42
|
+
if (isTextInput) {
|
|
43
|
+
setIsOpen(false);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (e.button === 0 && isOpen) {
|
|
47
|
+
setIsOpen(false);
|
|
48
|
+
(_b = window.getSelection()) === null || _b === void 0 ? void 0 : _b.removeAllRanges();
|
|
49
|
+
}
|
|
50
|
+
else if (selectedText && (openOnTextSelect || e.button === 2)) {
|
|
51
|
+
if (e.button === 2) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
}
|
|
54
|
+
setPosition({ x: e.clientX, y: e.clientY, text: selectedText });
|
|
55
|
+
setIsOpen(true);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
setIsOpen(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Add selectionchange listener to close menu if selection is cleared
|
|
62
|
+
const handleSelectionChange = () => {
|
|
63
|
+
var _a;
|
|
64
|
+
const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
|
|
65
|
+
if (!selectedText && isOpen) {
|
|
66
|
+
setIsOpen(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
70
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
71
|
+
document.addEventListener("contextmenu", handleMouseUp);
|
|
72
|
+
document.addEventListener("selectionchange", handleSelectionChange);
|
|
73
|
+
return () => {
|
|
74
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
75
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
76
|
+
document.removeEventListener("contextmenu", handleMouseUp);
|
|
77
|
+
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
78
|
+
};
|
|
79
|
+
}, [openOnTextSelect, isOpen, position.text]);
|
|
80
|
+
if (!isOpen) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return (_jsx("div", { ref: menuRef, className: "fixed bg-gray-400 dark:bg-gray-700 shadow-lg border border-gray-400 rounded-md overflow-hidden dark:text-white z-50", style: { top: position.y, left: position.x }, children: actions.map((action, index) => (_jsx(MenuEntryItem, { icon: action.icon, text: action.text, onClick: () => {
|
|
84
|
+
var _a;
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
(_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
|
|
87
|
+
client.event.emitSidebarAction(action.pluginId, action.actionKey, position.text);
|
|
88
|
+
} }, index))) }));
|
|
89
|
+
};
|
|
90
|
+
function MenuEntryItem(props) {
|
|
91
|
+
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 })] });
|
|
92
|
+
}
|
|
93
|
+
export default ContextMenu;
|
package/dist/core.d.ts
CHANGED
package/dist/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { Message, Tool } from "../controller/AIController";
|
|
3
3
|
export declare function useChat(tools?: Tool[]): {
|
|
4
4
|
messages: Message[];
|
|
5
5
|
append: (appendMessages: Message[]) => void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { EventBusMessage } from "./fromRimori/EventBus";
|
|
2
|
+
export type AccomplishmentMessage = EventBusMessage<MicroAccomplishmentPayload>;
|
|
3
|
+
export declare const skillCategories: readonly ["reading", "listening", "speaking", "writing", "learning", "community"];
|
|
4
|
+
interface BaseAccomplishmentPayload {
|
|
5
|
+
type: "micro" | "macro";
|
|
6
|
+
skillCategory: (typeof skillCategories)[number];
|
|
7
|
+
accomplishmentKeyword: string;
|
|
8
|
+
description: string;
|
|
9
|
+
meta?: {
|
|
10
|
+
key: string;
|
|
11
|
+
value: string | number | boolean;
|
|
12
|
+
description: string;
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
export interface MicroAccomplishmentPayload extends BaseAccomplishmentPayload {
|
|
16
|
+
type: "micro";
|
|
17
|
+
}
|
|
18
|
+
export interface MacroAccomplishmentPayload extends BaseAccomplishmentPayload {
|
|
19
|
+
type: "macro";
|
|
20
|
+
errorRatio: number;
|
|
21
|
+
durationMinutes: number;
|
|
22
|
+
}
|
|
23
|
+
export type AccomplishmentPayload = MicroAccomplishmentPayload | MacroAccomplishmentPayload;
|
|
24
|
+
export declare class AccomplishmentHandler {
|
|
25
|
+
private pluginId;
|
|
26
|
+
constructor(pluginId: string);
|
|
27
|
+
emitAccomplishment(payload: Omit<AccomplishmentPayload, "type">): void;
|
|
28
|
+
private validateAccomplishment;
|
|
29
|
+
private sanitizeAccomplishment;
|
|
30
|
+
private getDecoupledTopic;
|
|
31
|
+
/**
|
|
32
|
+
* Subscribe to accomplishment events
|
|
33
|
+
* @param accomplishmentTopic - The topic of the accomplishment event. The pattern can be any pattern of plugin.skillCategory.accomplishmentKeyword or an * as wildcard for any plugin, skill category or accomplishment keyword
|
|
34
|
+
* @param callback - The callback function to be called when the accomplishment event is triggered
|
|
35
|
+
*/
|
|
36
|
+
subscribe(accomplishmentTopics: string | string[] | undefined, callback: (payload: EventBusMessage<AccomplishmentPayload>) => void): void;
|
|
37
|
+
}
|
|
38
|
+
export {};
|