@rimori/client 1.0.5 → 1.1.1
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 +955 -28
- 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 +6 -4
- package/dist/components/ai/Avatar.js +14 -6
- package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +1 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +2 -1
- package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +36 -15
- 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 +15 -15
- package/dist/controller/SettingsController.js +15 -16
- 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/controller/SidePluginController.js +2 -1
- package/dist/core/components/ContextMenu.d.ts +10 -0
- package/dist/core/components/ContextMenu.js +93 -0
- package/dist/core.d.ts +1 -4
- package/dist/core.js +1 -4
- package/dist/hooks/UseChatHook.d.ts +1 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -8
- 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 +6 -3
- package/dist/plugin/RimoriClient.d.ts +92 -65
- package/dist/plugin/RimoriClient.js +105 -75
- package/dist/plugin/ThemeSetter.js +2 -2
- package/dist/plugin/fromRimori/EventBus.d.ts +6 -3
- package/dist/plugin/fromRimori/EventBus.js +15 -9
- package/dist/plugin/fromRimori/PluginTypes.d.ts +48 -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 +4 -4
- package/package.json +2 -3
- package/src/components/MarkdownEditor.tsx +78 -76
- package/src/components/ai/Assistant.tsx +1 -1
- package/src/components/ai/Avatar.tsx +66 -49
- package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +1 -1
- package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +82 -59
- 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 +83 -84
- package/src/controller/SharedContentController.ts +214 -53
- package/src/controller/SidePluginController.ts +3 -14
- package/src/core/components/ContextMenu.tsx +123 -0
- package/src/core.ts +1 -4
- package/src/hooks/UseChatHook.ts +17 -17
- package/src/index.ts +0 -8
- package/src/plugin/AccomplishmentHandler.ts +165 -0
- package/src/plugin/PluginController.ts +105 -103
- package/src/plugin/RimoriClient.ts +267 -250
- package/src/plugin/ThemeSetter.ts +2 -2
- package/src/plugin/fromRimori/EventBus.ts +23 -12
- package/src/plugin/fromRimori/PluginTypes.ts +64 -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 +4 -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
|
@@ -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[]>;
|
|
@@ -17,8 +17,9 @@ export function getPlugins(supabase) {
|
|
|
17
17
|
return (data || []).map((plugin) => ({
|
|
18
18
|
id: plugin.id,
|
|
19
19
|
title: plugin.title,
|
|
20
|
+
description: plugin.description,
|
|
20
21
|
icon_url: plugin.icon_url,
|
|
21
|
-
|
|
22
|
+
endpoint: plugin.endpoint,
|
|
22
23
|
context_menu_actions: plugin.context_menu_actions,
|
|
23
24
|
plugin_pages: plugin.plugin_pages,
|
|
24
25
|
sidebar_pages: plugin.sidebar_pages,
|
|
@@ -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
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
export * from "./controller/AIController";
|
|
2
|
-
export * from "./controller/SharedContentController";
|
|
3
|
-
export * from "./controller/SettingsController";
|
|
4
1
|
export * from "./plugin/RimoriClient";
|
|
5
2
|
export * from "./plugin/PluginController";
|
|
6
3
|
export * from "./utils/difficultyConverter";
|
|
7
4
|
export * from "./utils/PluginUtils";
|
|
8
5
|
export * from "./worker/WorkerSetup";
|
|
9
|
-
export * from "./
|
|
6
|
+
export * from "./utils/Language";
|
package/dist/core.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// Core functionality exports
|
|
2
|
-
export * from "./controller/AIController";
|
|
3
|
-
export * from "./controller/SharedContentController";
|
|
4
|
-
export * from "./controller/SettingsController";
|
|
5
2
|
export * from "./plugin/RimoriClient";
|
|
6
3
|
export * from "./plugin/PluginController";
|
|
7
4
|
export * from "./utils/difficultyConverter";
|
|
8
5
|
export * from "./utils/PluginUtils";
|
|
9
6
|
export * from "./worker/WorkerSetup";
|
|
10
|
-
export * from "./
|
|
7
|
+
export * from "./utils/Language";
|
|
@@ -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;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
export * from './core';
|
|
2
2
|
export * from './components';
|
|
3
|
-
export * from "./components/MarkdownEditor";
|
|
4
|
-
export * from "./components/CRUDModal";
|
|
5
|
-
export * from "./components/Spinner";
|
|
6
|
-
export * from "./components/audio/Playbutton";
|
|
7
|
-
export * from "./controller/AIController";
|
|
8
|
-
export * from "./controller/SharedContentController";
|
|
9
|
-
export * from "./controller/SettingsController";
|
|
10
3
|
export * from "./hooks/UseChatHook";
|
|
11
4
|
export * from "./plugin/RimoriClient";
|
|
12
|
-
export * from "./plugin/ThemeSetter";
|
|
13
5
|
export * from "./providers/PluginProvider";
|
|
14
6
|
export * from "./utils/difficultyConverter";
|
|
15
7
|
export * from "./utils/PluginUtils";
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
// Re-export everything
|
|
2
2
|
export * from './core';
|
|
3
3
|
export * from './components';
|
|
4
|
-
export * from "./components/MarkdownEditor";
|
|
5
|
-
export * from "./components/CRUDModal";
|
|
6
|
-
export * from "./components/Spinner";
|
|
7
|
-
export * from "./components/audio/Playbutton";
|
|
8
|
-
export * from "./controller/AIController";
|
|
9
|
-
export * from "./controller/SharedContentController";
|
|
10
|
-
export * from "./controller/SettingsController";
|
|
11
4
|
export * from "./hooks/UseChatHook";
|
|
12
5
|
export * from "./plugin/RimoriClient";
|
|
13
|
-
export * from "./plugin/ThemeSetter";
|
|
14
6
|
export * from "./providers/PluginProvider";
|
|
15
7
|
export * from "./utils/difficultyConverter";
|
|
16
8
|
export * from "./utils/PluginUtils";
|
|
@@ -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 {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { EventBus } from "./fromRimori/EventBus";
|
|
2
|
+
export const skillCategories = ["reading", "listening", "speaking", "writing", "learning", "community"];
|
|
3
|
+
export class AccomplishmentHandler {
|
|
4
|
+
constructor(pluginId) {
|
|
5
|
+
this.pluginId = pluginId;
|
|
6
|
+
}
|
|
7
|
+
emitAccomplishment(payload) {
|
|
8
|
+
const accomplishmentPayload = Object.assign(Object.assign({}, payload), { type: "durationMinutes" in payload ? "macro" : "micro" });
|
|
9
|
+
this.validateAccomplishment(accomplishmentPayload);
|
|
10
|
+
const sanitizedPayload = this.sanitizeAccomplishment(accomplishmentPayload);
|
|
11
|
+
const topic = "global.accomplishment.trigger" + (accomplishmentPayload.type === "macro" ? "Macro" : "Micro");
|
|
12
|
+
EventBus.emit(this.pluginId, topic, sanitizedPayload);
|
|
13
|
+
}
|
|
14
|
+
validateAccomplishment(payload) {
|
|
15
|
+
if (!skillCategories.includes(payload.skillCategory)) {
|
|
16
|
+
throw new Error(`Invalid skill category: ${payload.skillCategory}`);
|
|
17
|
+
}
|
|
18
|
+
//regex validate accomplishmentKeyword
|
|
19
|
+
if (!/^[a-z_-]+$/.test(payload.accomplishmentKeyword)) {
|
|
20
|
+
throw new Error(`The accomplishment keyword: ${payload.accomplishmentKeyword} is invalid. Only lowercase letters, minuses and underscores are allowed`);
|
|
21
|
+
}
|
|
22
|
+
//description is required
|
|
23
|
+
if (payload.description.length < 10) {
|
|
24
|
+
throw new Error("Description is too short");
|
|
25
|
+
}
|
|
26
|
+
//check that the type is valid
|
|
27
|
+
if (!["micro", "macro"].includes(payload.type)) {
|
|
28
|
+
throw new Error("Invalid accomplishment type " + payload.type);
|
|
29
|
+
}
|
|
30
|
+
//durationMinutes is required
|
|
31
|
+
if (payload.type === "macro" && payload.durationMinutes < 4) {
|
|
32
|
+
throw new Error("The duration must be at least 4 minutes");
|
|
33
|
+
}
|
|
34
|
+
//errorRatio is required
|
|
35
|
+
if (payload.type === "macro" && (payload.errorRatio < 0 || payload.errorRatio > 1)) {
|
|
36
|
+
throw new Error("The error ratio must be between 0 and 1");
|
|
37
|
+
}
|
|
38
|
+
//regex check meta data key
|
|
39
|
+
if (payload.meta) {
|
|
40
|
+
payload.meta.forEach(meta => {
|
|
41
|
+
if (!/^[a-z_]+$/.test(meta.key)) {
|
|
42
|
+
throw new Error("Invalid meta data key " + meta.key + ", only lowercase letters and underscores are allowed");
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
sanitizeAccomplishment(payload) {
|
|
48
|
+
var _a;
|
|
49
|
+
payload.description = payload.description.replace(/[^\x20-\x7E]/g, "");
|
|
50
|
+
(_a = payload.meta) === null || _a === void 0 ? void 0 : _a.forEach((meta) => {
|
|
51
|
+
meta.description = meta.description.replace(/[^\x20-\x7E]/g, "");
|
|
52
|
+
});
|
|
53
|
+
return payload;
|
|
54
|
+
}
|
|
55
|
+
getDecoupledTopic(topic) {
|
|
56
|
+
const [plugin, skillCategory, accomplishmentKeyword] = topic.split(".");
|
|
57
|
+
return { plugin: plugin || "*", skillCategory: skillCategory || "*", accomplishmentKeyword: accomplishmentKeyword || "*" };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Subscribe to accomplishment events
|
|
61
|
+
* @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
|
|
62
|
+
* @param callback - The callback function to be called when the accomplishment event is triggered
|
|
63
|
+
*/
|
|
64
|
+
subscribe(accomplishmentTopics = "*", callback) {
|
|
65
|
+
if (typeof accomplishmentTopics === "string") {
|
|
66
|
+
accomplishmentTopics = [accomplishmentTopics];
|
|
67
|
+
}
|
|
68
|
+
accomplishmentTopics.forEach((accomplishmentTopic) => {
|
|
69
|
+
const topicLength = accomplishmentTopic.split(".").length;
|
|
70
|
+
if (topicLength === 1) {
|
|
71
|
+
accomplishmentTopic += ".*.*";
|
|
72
|
+
}
|
|
73
|
+
else if (topicLength === 2) {
|
|
74
|
+
accomplishmentTopic += ".*";
|
|
75
|
+
}
|
|
76
|
+
else if (topicLength !== 3) {
|
|
77
|
+
throw new Error("Invalid accomplishment topic pattern. The pattern must be plugin.skillCategory.accomplishmentKeyword or an * as wildcard for any plugin, skill category or accomplishment keyword");
|
|
78
|
+
}
|
|
79
|
+
EventBus.on(["global.accomplishment.triggerMicro", "global.accomplishment.triggerMacro"], (event) => {
|
|
80
|
+
const { plugin, skillCategory, accomplishmentKeyword } = this.getDecoupledTopic(accomplishmentTopic);
|
|
81
|
+
if (plugin !== "*" && event.sender !== plugin)
|
|
82
|
+
return;
|
|
83
|
+
if (skillCategory !== "*" && event.data.skillCategory !== skillCategory)
|
|
84
|
+
return;
|
|
85
|
+
if (accomplishmentKeyword !== "*" && event.data.accomplishmentKeyword !== accomplishmentKeyword)
|
|
86
|
+
return;
|
|
87
|
+
callback(event);
|
|
88
|
+
}, [this.pluginId]);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// const accomplishmentHandler = AccomplishmentHandler.getInstance("my-plugin");
|
|
93
|
+
// accomplishmentHandler.subscribe("*", (payload) => {
|
|
94
|
+
// console.log(payload);
|
|
95
|
+
// });
|
|
96
|
+
// accomplishmentHandler.emitAccomplishment({
|
|
97
|
+
// skillCategory: "reading",
|
|
98
|
+
// accomplishmentKeyword: "chapter",
|
|
99
|
+
// description: "Read chapter 1 of the book",
|
|
100
|
+
// durationMinutes: 10,
|
|
101
|
+
// meta: [
|
|
102
|
+
// {
|
|
103
|
+
// key: "book",
|
|
104
|
+
// value: "The Great Gatsby",
|
|
105
|
+
// description: "The book I read",
|
|
106
|
+
// },
|
|
107
|
+
// ],
|
|
108
|
+
// });
|