@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.
Files changed (101) hide show
  1. package/README.md +955 -28
  2. package/dist/components/MarkdownEditor.js +6 -4
  3. package/dist/components/PluginController.d.ts +21 -0
  4. package/dist/components/PluginController.js +116 -0
  5. package/dist/components/ai/Assistant.js +1 -1
  6. package/dist/components/ai/Avatar.d.ts +6 -4
  7. package/dist/components/ai/Avatar.js +14 -6
  8. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +1 -1
  9. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +2 -1
  10. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +36 -15
  11. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -0
  12. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +3 -0
  13. package/dist/components/ai/EmbeddedAssistent/TTS/Player.d.ts +2 -0
  14. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +5 -0
  15. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +3 -0
  16. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +41 -5
  17. package/dist/components/ai/utils.d.ts +1 -1
  18. package/dist/components.d.ts +1 -0
  19. package/dist/components.js +1 -0
  20. package/dist/controller/AIController.js +2 -1
  21. package/dist/controller/SettingsController.d.ts +15 -15
  22. package/dist/controller/SettingsController.js +15 -16
  23. package/dist/controller/SharedContentController.d.ts +58 -11
  24. package/dist/controller/SharedContentController.js +161 -26
  25. package/dist/controller/SidePluginController.d.ts +1 -12
  26. package/dist/controller/SidePluginController.js +2 -1
  27. package/dist/core/components/ContextMenu.d.ts +10 -0
  28. package/dist/core/components/ContextMenu.js +93 -0
  29. package/dist/core.d.ts +1 -4
  30. package/dist/core.js +1 -4
  31. package/dist/hooks/UseChatHook.d.ts +1 -1
  32. package/dist/index.d.ts +0 -8
  33. package/dist/index.js +0 -8
  34. package/dist/plugin/AccomplishmentHandler.d.ts +38 -0
  35. package/dist/plugin/AccomplishmentHandler.js +108 -0
  36. package/dist/plugin/ContextMenu.d.ts +17 -0
  37. package/dist/plugin/ContextMenu.js +45 -0
  38. package/dist/plugin/PluginController.js +6 -3
  39. package/dist/plugin/RimoriClient.d.ts +92 -65
  40. package/dist/plugin/RimoriClient.js +105 -75
  41. package/dist/plugin/ThemeSetter.js +2 -2
  42. package/dist/plugin/fromRimori/EventBus.d.ts +6 -3
  43. package/dist/plugin/fromRimori/EventBus.js +15 -9
  44. package/dist/plugin/fromRimori/PluginTypes.d.ts +48 -0
  45. package/dist/plugin/fromRimori/PluginTypes.js +1 -0
  46. package/dist/providers/PluginController.d.ts +21 -0
  47. package/dist/providers/PluginController.js +116 -0
  48. package/dist/providers/PluginProvider.js +26 -73
  49. package/dist/types/Actions.d.ts +4 -0
  50. package/dist/types/Actions.js +1 -0
  51. package/dist/utils/Language.d.ts +66 -0
  52. package/dist/utils/Language.js +67 -0
  53. package/dist/utils/difficultyConverter.d.ts +1 -0
  54. package/dist/utils/difficultyConverter.js +3 -0
  55. package/dist/worker/WorkerSetup.js +4 -4
  56. package/package.json +2 -3
  57. package/src/components/MarkdownEditor.tsx +78 -76
  58. package/src/components/ai/Assistant.tsx +1 -1
  59. package/src/components/ai/Avatar.tsx +66 -49
  60. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +1 -1
  61. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +82 -59
  62. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +4 -0
  63. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +6 -0
  64. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +51 -8
  65. package/src/components/ai/utils.ts +1 -1
  66. package/src/components.ts +2 -1
  67. package/src/controller/AIController.ts +2 -1
  68. package/src/controller/SettingsController.ts +83 -84
  69. package/src/controller/SharedContentController.ts +214 -53
  70. package/src/controller/SidePluginController.ts +3 -14
  71. package/src/core/components/ContextMenu.tsx +123 -0
  72. package/src/core.ts +1 -4
  73. package/src/hooks/UseChatHook.ts +17 -17
  74. package/src/index.ts +0 -8
  75. package/src/plugin/AccomplishmentHandler.ts +165 -0
  76. package/src/plugin/PluginController.ts +105 -103
  77. package/src/plugin/RimoriClient.ts +267 -250
  78. package/src/plugin/ThemeSetter.ts +2 -2
  79. package/src/plugin/fromRimori/EventBus.ts +23 -12
  80. package/src/plugin/fromRimori/PluginTypes.ts +64 -0
  81. package/src/providers/PluginProvider.tsx +63 -110
  82. package/src/types/Actions.ts +6 -0
  83. package/src/utils/Language.ts +70 -0
  84. package/src/utils/difficultyConverter.ts +4 -0
  85. package/src/worker/WorkerSetup.ts +4 -4
  86. package/dist/components/avatar/Assistant.d.ts +0 -9
  87. package/dist/components/avatar/Assistant.js +0 -59
  88. package/dist/components/avatar/Avatar.d.ts +0 -12
  89. package/dist/components/avatar/Avatar.js +0 -42
  90. package/dist/components/avatar/EmbeddedAssistent/AudioInputField.d.ts +0 -7
  91. package/dist/components/avatar/EmbeddedAssistent/AudioInputField.js +0 -38
  92. package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.d.ts +0 -7
  93. package/dist/components/avatar/EmbeddedAssistent/CircleAudioAvatar.js +0 -59
  94. package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.d.ts +0 -19
  95. package/dist/components/avatar/EmbeddedAssistent/TTS/MessageSender.js +0 -84
  96. package/dist/components/avatar/EmbeddedAssistent/TTS/Player.d.ts +0 -25
  97. package/dist/components/avatar/EmbeddedAssistent/TTS/Player.js +0 -180
  98. package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.d.ts +0 -7
  99. package/dist/components/avatar/EmbeddedAssistent/VoiceRecoder.js +0 -45
  100. package/dist/components/avatar/utils.d.ts +0 -6
  101. package/dist/components/avatar/utils.js +0 -14
@@ -1,22 +1,69 @@
1
- import { ObjectRequest } from "./ObjectController";
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
2
  import { RimoriClient } from "../plugin/RimoriClient";
3
- export interface BasicAssignment {
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
- fetchNewSharedContent<T, R = T & BasicAssignment>(type: string, generatorInstructions: (reservedTopics: string[]) => Promise<ObjectRequest> | ObjectRequest, filter?: {
15
- column: string;
16
- value: string | number | boolean;
17
- }): Promise<R[]>;
18
- private getReservedTopics;
19
- private purifyStringArray;
20
- getSharedContent<T extends BasicAssignment>(type: string, id: string): Promise<T>;
21
- completeSharedContent(type: string, assignmentId: string): Promise<void>;
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
- fetchNewSharedContent(type, generatorInstructions, filter) {
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 queryParameter = { filter_column: (filter === null || filter === void 0 ? void 0 : filter.column) || null, filter_value: (filter === null || filter === void 0 ? void 0 : filter.value) || null, unread: true };
18
- const { data: newAssignments } = yield this.rimoriClient.db.rpc(type + "_entries", queryParameter);
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
- return newAssignments;
42
+ const index = Math.floor(Math.random() * newAssignments.length);
43
+ return newAssignments[index];
22
44
  }
23
45
  // generate new assignments
24
- const { data: oldAssignments } = yield this.rimoriClient.db.rpc(type + "_entries", Object.assign(Object.assign({}, queryParameter), { unread: false }));
25
- console.log('oldAssignments:', oldAssignments);
26
- const reservedTopics = this.getReservedTopics(oldAssignments);
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 preparedData = Object.assign(Object.assign({ id: uuidv4() }, instructions), { keywords: this.purifyStringArray(instructions.keywords) });
34
- return yield this.rimoriClient.db.from(type).insert(preparedData).then(() => [preparedData]);
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
- getReservedTopics(oldAssignments) {
38
- return oldAssignments.map(({ topic, keywords }) => {
39
- const keywordTexts = this.purifyStringArray(keywords).join(',');
40
- return `${topic}(${keywordTexts})`;
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
- purifyStringArray(array) {
44
- return array.map(({ text }) => text);
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
- getSharedContent(type, id) {
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
- return yield this.rimoriClient.db.from(type).select().eq('id', id).single();
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
- completeSharedContent(type, assignmentId) {
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
- yield this.rimoriClient.db.from(type + "_result").insert({ assignment_id: assignmentId });
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
- export interface Plugin {
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
- website: plugin.website,
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 "./plugin/fromRimori/EventBus";
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 "./plugin/fromRimori/EventBus";
7
+ export * from "./utils/Language";
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Tool, Message } from "../controller/AIController";
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
+ // });