@rimori/client 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +128 -45
  2. package/dist/cli/scripts/init/dev-registration.d.ts +35 -0
  3. package/dist/cli/scripts/init/dev-registration.js +175 -0
  4. package/dist/cli/scripts/init/env-setup.d.ts +9 -0
  5. package/dist/cli/scripts/init/env-setup.js +43 -0
  6. package/dist/cli/scripts/init/file-operations.d.ts +4 -0
  7. package/dist/cli/scripts/init/file-operations.js +51 -0
  8. package/dist/cli/scripts/init/html-cleaner.d.ts +4 -0
  9. package/dist/cli/scripts/init/html-cleaner.js +38 -0
  10. package/dist/cli/scripts/init/main.d.ts +2 -0
  11. package/dist/cli/scripts/init/main.js +159 -0
  12. package/dist/cli/scripts/init/package-setup.d.ts +32 -0
  13. package/dist/cli/scripts/init/package-setup.js +75 -0
  14. package/dist/cli/scripts/init/router-transformer.d.ts +6 -0
  15. package/dist/cli/scripts/init/router-transformer.js +254 -0
  16. package/dist/cli/scripts/init/tailwind-config.d.ts +4 -0
  17. package/dist/cli/scripts/init/tailwind-config.js +56 -0
  18. package/dist/cli/scripts/init/vite-config.d.ts +20 -0
  19. package/dist/cli/scripts/init/vite-config.js +54 -0
  20. package/dist/cli/scripts/release/release-config-upload.d.ts +7 -0
  21. package/dist/cli/scripts/release/release-config-upload.js +116 -0
  22. package/dist/cli/scripts/release/release-db-update.d.ts +6 -0
  23. package/dist/cli/scripts/release/release-db-update.js +100 -0
  24. package/dist/cli/scripts/release/release-file-upload.d.ts +6 -0
  25. package/dist/cli/scripts/release/release-file-upload.js +136 -0
  26. package/dist/cli/scripts/release/release.d.ts +23 -0
  27. package/dist/cli/scripts/release/release.js +70 -0
  28. package/dist/cli/types/DatabaseTypes.d.ts +103 -0
  29. package/dist/cli/types/DatabaseTypes.js +2 -0
  30. package/dist/components/ai/Assistant.js +4 -4
  31. package/dist/components/ai/Avatar.d.ts +3 -2
  32. package/dist/components/ai/Avatar.js +10 -5
  33. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -1
  34. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.d.ts +1 -0
  35. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +12 -6
  36. package/dist/components/ai/utils.js +0 -1
  37. package/dist/components/audio/Playbutton.js +3 -3
  38. package/dist/{core → components}/components/ContextMenu.js +2 -2
  39. package/dist/components.d.ts +5 -5
  40. package/dist/components.js +5 -5
  41. package/dist/core/controller/AIController.d.ts +15 -0
  42. package/dist/core/controller/AIController.js +120 -0
  43. package/dist/{controller → core/controller}/ObjectController.d.ts +8 -0
  44. package/dist/{controller → core/controller}/SettingsController.d.ts +12 -4
  45. package/dist/{controller → core/controller}/SettingsController.js +0 -25
  46. package/dist/{controller → core/controller}/SharedContentController.d.ts +10 -19
  47. package/dist/{controller → core/controller}/SharedContentController.js +11 -11
  48. package/dist/core/core.d.ts +13 -0
  49. package/dist/core/core.js +8 -0
  50. package/dist/{plugin/fromRimori → fromRimori}/EventBus.d.ts +3 -3
  51. package/dist/{plugin/fromRimori → fromRimori}/EventBus.js +25 -8
  52. package/dist/fromRimori/PluginTypes.d.ts +171 -0
  53. package/dist/hooks/UseChatHook.d.ts +2 -1
  54. package/dist/hooks/UseChatHook.js +3 -3
  55. package/dist/index.d.ts +5 -3
  56. package/dist/index.js +4 -3
  57. package/dist/plugin/AccomplishmentHandler.d.ts +1 -1
  58. package/dist/plugin/AccomplishmentHandler.js +1 -1
  59. package/dist/plugin/PluginController.d.ts +16 -3
  60. package/dist/plugin/PluginController.js +24 -18
  61. package/dist/plugin/RimoriClient.d.ts +22 -17
  62. package/dist/plugin/RimoriClient.js +35 -25
  63. package/dist/plugin/StandaloneClient.js +11 -8
  64. package/dist/plugin/ThemeSetter.d.ts +1 -0
  65. package/dist/plugin/ThemeSetter.js +9 -6
  66. package/dist/providers/PluginProvider.d.ts +3 -0
  67. package/dist/providers/PluginProvider.js +4 -4
  68. package/dist/utils/Language.d.ts +2 -1
  69. package/dist/utils/Language.js +4 -2
  70. package/dist/utils/difficultyConverter.js +1 -1
  71. package/dist/utils/endpoint.d.ts +2 -0
  72. package/dist/utils/endpoint.js +2 -0
  73. package/dist/worker/WorkerSetup.js +3 -1
  74. package/example/docs/devdocs.md +231 -0
  75. package/example/docs/overview.md +29 -0
  76. package/example/docs/userdocs.md +123 -0
  77. package/example/rimori.config.ts +89 -0
  78. package/example/worker/vite.config.ts +23 -0
  79. package/example/worker/worker.ts +11 -0
  80. package/package.json +15 -9
  81. package/src/cli/scripts/init/dev-registration.ts +193 -0
  82. package/src/cli/scripts/init/env-setup.ts +44 -0
  83. package/src/cli/scripts/init/file-operations.ts +58 -0
  84. package/src/cli/scripts/init/html-cleaner.ts +48 -0
  85. package/src/cli/scripts/init/main.ts +171 -0
  86. package/src/cli/scripts/init/package-setup.ts +117 -0
  87. package/src/cli/scripts/init/router-transformer.ts +329 -0
  88. package/src/cli/scripts/init/tailwind-config.ts +75 -0
  89. package/src/cli/scripts/init/vite-config.ts +73 -0
  90. package/src/cli/scripts/release/release-config-upload.ts +114 -0
  91. package/src/cli/scripts/release/release-db-update.ts +97 -0
  92. package/src/cli/scripts/release/release-file-upload.ts +138 -0
  93. package/src/cli/scripts/release/release.ts +69 -0
  94. package/src/cli/types/DatabaseTypes.ts +117 -0
  95. package/src/components/ai/Assistant.tsx +4 -4
  96. package/src/components/ai/Avatar.tsx +24 -7
  97. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +1 -1
  98. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +16 -8
  99. package/src/components/ai/utils.ts +0 -2
  100. package/src/components/audio/Playbutton.tsx +3 -3
  101. package/src/{core → components}/components/ContextMenu.tsx +3 -3
  102. package/src/components.ts +6 -6
  103. package/src/core/controller/AIController.ts +122 -0
  104. package/src/core/controller/ObjectController.ts +115 -0
  105. package/src/{controller → core/controller}/SettingsController.ts +13 -29
  106. package/src/{controller → core/controller}/SharedContentController.ts +18 -28
  107. package/src/core/core.ts +15 -0
  108. package/src/{plugin/fromRimori → fromRimori}/EventBus.ts +28 -10
  109. package/src/fromRimori/PluginTypes.ts +203 -0
  110. package/src/hooks/UseChatHook.ts +5 -4
  111. package/src/index.ts +5 -3
  112. package/src/plugin/AccomplishmentHandler.ts +1 -1
  113. package/src/plugin/PluginController.ts +35 -23
  114. package/src/plugin/RimoriClient.ts +48 -41
  115. package/src/plugin/StandaloneClient.ts +11 -8
  116. package/src/plugin/ThemeSetter.ts +12 -8
  117. package/src/providers/PluginProvider.tsx +7 -4
  118. package/src/utils/Language.ts +4 -2
  119. package/src/utils/difficultyConverter.ts +3 -3
  120. package/src/utils/endpoint.ts +2 -0
  121. package/src/worker/WorkerSetup.ts +4 -2
  122. package/dist/components/PluginController.d.ts +0 -21
  123. package/dist/components/PluginController.js +0 -116
  124. package/dist/controller/AIController.d.ts +0 -23
  125. package/dist/controller/AIController.js +0 -93
  126. package/dist/controller/SidePluginController.d.ts +0 -3
  127. package/dist/controller/SidePluginController.js +0 -31
  128. package/dist/core.d.ts +0 -7
  129. package/dist/core.js +0 -7
  130. package/dist/plugin/ContextMenu.d.ts +0 -17
  131. package/dist/plugin/ContextMenu.js +0 -45
  132. package/dist/plugin/fromRimori/PluginTypes.d.ts +0 -48
  133. package/dist/plugin/fromRimori/SupabaseHandler.d.ts +0 -13
  134. package/dist/plugin/fromRimori/SupabaseHandler.js +0 -55
  135. package/dist/providers/PluginController.d.ts +0 -21
  136. package/dist/providers/PluginController.js +0 -116
  137. package/dist/types/Actions.d.ts +0 -4
  138. package/dist/types/Actions.js +0 -1
  139. package/src/controller/AIController.ts +0 -112
  140. package/src/controller/ObjectController.ts +0 -107
  141. package/src/controller/SidePluginController.ts +0 -25
  142. package/src/core.ts +0 -8
  143. package/src/plugin/fromRimori/PluginTypes.ts +0 -64
  144. package/src/types/Actions.ts +0 -6
  145. /package/dist/{core → components}/components/ContextMenu.d.ts +0 -0
  146. /package/dist/{controller → core/controller}/ObjectController.js +0 -0
  147. /package/dist/{controller → core/controller}/VoiceController.d.ts +0 -0
  148. /package/dist/{controller → core/controller}/VoiceController.js +0 -0
  149. /package/dist/{plugin/fromRimori → fromRimori}/PluginTypes.js +0 -0
  150. /package/src/{controller → core/controller}/VoiceController.ts +0 -0
  151. /package/src/{plugin/fromRimori → fromRimori}/readme.md +0 -0
@@ -0,0 +1,138 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { Config } from './release.js';
4
+
5
+ /**
6
+ * Upload all files from a directory and its subdirectories to the release function
7
+ * @param config - Configuration object
8
+ */
9
+ export async function uploadDirectory(config: Config, release_id: string): Promise<void> {
10
+ const relativePath = './dist';
11
+
12
+ console.log(`📁 Preparing to upload files from ${relativePath}...`);
13
+
14
+ // Check if dist directory exists
15
+ try {
16
+ await fs.promises.access(relativePath);
17
+ } catch (e) {
18
+ throw new Error(`Directory ${relativePath} does not exist. Make sure to build your plugin first.`);
19
+ }
20
+ // Get all files recursively
21
+ const files = await getAllFiles(relativePath);
22
+
23
+ if (files.length === 0) {
24
+ console.log('⚠️ No files found to upload');
25
+ return;
26
+ }
27
+
28
+ console.log(`🚀 Uploading ${files.length} files...`);
29
+
30
+ // Create FormData
31
+ const formData = new FormData();
32
+
33
+ // Add version and release channel data
34
+ formData.append('version', config.version);
35
+ formData.append('release_channel', config.release_channel);
36
+ formData.append('plugin_id', config.plugin_id);
37
+
38
+ // Create path mapping with IDs as keys
39
+ const pathMapping: Record<string, string> = {};
40
+
41
+ for (let i = 0; i < files.length; i++) {
42
+ const filePath = files[i];
43
+ try {
44
+ const fileContent = await fs.promises.readFile(filePath);
45
+ const relativePath = path.relative('./dist', filePath);
46
+ const contentType = getContentType(filePath);
47
+
48
+ // Generate unique ID for this file
49
+ const fileId = `file_${i}`;
50
+
51
+ // Add to path mapping using ID as key
52
+ pathMapping[fileId] = relativePath;
53
+
54
+ // Create a Blob with the file content and content type
55
+ const blob = new Blob([fileContent], { type: contentType });
56
+
57
+ // Add file to FormData with ID_filename format
58
+ const fileName = `${fileId}_${path.basename(filePath)}`;
59
+ formData.append('files', blob, fileName);
60
+ } catch (error: any) {
61
+ console.error(`❌ Error reading file ${filePath}:`, error.message);
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ // Add path mapping to FormData
67
+ formData.append('path_mapping', JSON.stringify(pathMapping));
68
+
69
+ // Upload to the release endpoint
70
+ const response = await fetch(`${config.domain}/release/${release_id}/files`, {
71
+ method: 'POST',
72
+ headers: { 'Authorization': `Bearer ${config.token}` },
73
+ body: formData,
74
+ });
75
+
76
+ if (response.ok) {
77
+ console.log('✅ Files uploaded successfully!');
78
+ } else {
79
+ const errorText = await response.text();
80
+ console.log('❌ File upload failed!');
81
+ console.log('Response:', errorText);
82
+ throw new Error(`File upload failed with status ${response.status}`);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Recursively get all files from a directory
88
+ */
89
+ async function getAllFiles(dirPath: string): Promise<string[]> {
90
+ const files: string[] = [];
91
+
92
+ async function traverse(currentPath: string) {
93
+ const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
94
+
95
+ for (const entry of entries) {
96
+ const fullPath = path.join(currentPath, entry.name);
97
+
98
+ if (entry.isDirectory()) {
99
+ await traverse(fullPath);
100
+ } else if (entry.isFile()) {
101
+ files.push(fullPath);
102
+ }
103
+ }
104
+ }
105
+
106
+ await traverse(dirPath);
107
+ return files;
108
+ }
109
+
110
+ /**
111
+ * Get content type based on file extension
112
+ */
113
+ function getContentType(filePath: string): string {
114
+ const ext = filePath.split('.').pop()?.toLowerCase();
115
+ const contentTypes: Record<string, string> = {
116
+ html: 'text/html',
117
+ css: 'text/css',
118
+ js: 'application/javascript',
119
+ json: 'application/json',
120
+ md: 'text/markdown',
121
+ txt: 'text/plain',
122
+ png: 'image/png',
123
+ jpg: 'image/jpeg',
124
+ jpeg: 'image/jpeg',
125
+ gif: 'image/gif',
126
+ svg: 'image/svg+xml',
127
+ pdf: 'application/pdf',
128
+ ico: 'image/x-icon',
129
+ mp3: 'audio/mpeg',
130
+ wav: 'audio/wav',
131
+ ogg: 'audio/ogg',
132
+ m4a: 'audio/mp4',
133
+ webp: 'image/webp',
134
+ };
135
+ const contentType = contentTypes[ext || ''];
136
+ if (!contentType) throw new Error(`Unsupported file type: ${ext}`);
137
+ return contentType;
138
+ }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Usage:
5
+ * rimori-release <release_channel>
6
+ *
7
+ * Environment variables required:
8
+ * RIMORI_TOKEN - Your Rimori token
9
+ * RIMORI_PLUGIN - Your plugin ID
10
+ *
11
+ * Make sure to install dependencies:
12
+ * npm install node-fetch form-data ts-node typescript
13
+ */
14
+
15
+ import 'dotenv/config';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import dbUpdate from './release-db-update.js';
19
+ import { uploadDirectory } from './release-file-upload.js';
20
+ import { releasePlugin, sendConfiguration } from './release-config-upload.js';
21
+
22
+ // Read version from package.json
23
+ const packageJson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf8'));
24
+ const { version, r_id: pluginId } = packageJson;
25
+
26
+ const RIMORI_TOKEN = process.env.RIMORI_TOKEN;
27
+ if (!RIMORI_TOKEN) throw new Error('RIMORI_TOKEN is not set');
28
+ if (!pluginId) throw new Error('The plugin id (r_id) is not set in package.json');
29
+
30
+ const [releaseChannel] = process.argv.slice(2);
31
+ if (!releaseChannel) {
32
+ console.error('Usage: rimori-release <release_channel>');
33
+ process.exit(1);
34
+ }
35
+
36
+ const config = {
37
+ version,
38
+ release_channel: releaseChannel,
39
+ plugin_id: pluginId,
40
+ token: RIMORI_TOKEN,
41
+ domain: process.env.RIMORI_BACKEND_URL || "https://api.rimori.se",
42
+ rimori_client_version: packageJson.dependencies['@rimori/client'].replace('^', ''),
43
+ }
44
+
45
+ export type Config = typeof config;
46
+
47
+ /**
48
+ * Main release process
49
+ */
50
+ async function releaseProcess(): Promise<void> {
51
+ try {
52
+ console.log(`🚀 Releasing ${config.plugin_id} to ${config.release_channel}...`);
53
+ // First send the configuration
54
+ const release_id = await sendConfiguration(config);
55
+
56
+ await dbUpdate(config, release_id);
57
+
58
+ // Then upload the files
59
+ await uploadDirectory(config, release_id);
60
+
61
+ // Then release the plugin
62
+ await releasePlugin(config, release_id);
63
+ } catch (error: any) {
64
+ console.log("❌ Error:", error.message);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ releaseProcess();
@@ -0,0 +1,117 @@
1
+
2
+ // Database table structure definitions
3
+
4
+ /**
5
+ * Supported database column data types for table schema definitions.
6
+ */
7
+ type DbColumnType = 'decimal' | 'integer' | 'text' | 'boolean' | 'json' | 'timestamp' | 'uuid';
8
+
9
+ /**
10
+ * Foreign key relationship configuration with cascade delete support.
11
+ * Defines a relationship where the source record is deleted when the destination record is deleted.
12
+ */
13
+ interface ForeignKeyRelation {
14
+ /** The target table that this column references */
15
+ references_table: string;
16
+ /** The target column in the referenced table (defaults to 'id') */
17
+ references_column?: string;
18
+ /** Whether to cascade delete when the referenced record is deleted */
19
+ on_delete_cascade: boolean;
20
+ }
21
+
22
+ /**
23
+ * Database column definition with support for types, constraints, and relationships.
24
+ */
25
+ export interface DbColumnDefinition {
26
+ /** The data type of the column */
27
+ type: DbColumnType;
28
+ /** Human-readable description of the column's purpose */
29
+ description: string;
30
+ /** Whether the column can contain null values */
31
+ nullable?: boolean;
32
+ /** Whether the column has a unique constraint */
33
+ unique?: boolean;
34
+ /** Default value for the column. can also use sql functions like now(), auth.uid() or gen_random_uuid() */
35
+ default_value?: string | number | boolean;
36
+ /** Array of allowed values for enumerated columns */
37
+ // enum?: string[];
38
+ /** Foreign key relationship configuration */
39
+ foreign_key?: ForeignKeyRelation;
40
+ /** The name of the column before it was renamed. */
41
+ old_name?: string;
42
+ /** Whether the column is deprecated. The column gets renamed to column_name_old. To fully remove the column, first set deprecated to true and then after a release, remove the column from the table definition. */
43
+ deprecated?: boolean;
44
+ /** Whether the column is a primary key */
45
+ // primary_key?: boolean;
46
+ /** Restrictions for the column. If the column is restricted, the permission is further restricted. E.g. if the column is restricted to user, then the user can only read the column if they have the right permission.
47
+ * Example: Denying users to update the column, but allowing the moderator to update the column.
48
+ */
49
+ restrict?: {
50
+ /** Restrictions for the user */
51
+ user: Partial<Omit<DbPermissionDefinition, 'delete'>>,
52
+ /** Restrictions for the moderator */
53
+ moderator?: Partial<Omit<DbPermissionDefinition, 'delete'>>,
54
+ /** Restrictions for the maintainer */
55
+ // maintainer?: Partial<DbPermissionDefinition>,
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Base table structure that all database tables inherit.
61
+ * Includes standard audit fields for tracking creation and ownership.
62
+ */
63
+ interface BaseTableStructure {
64
+ /** Unique identifier for the record */
65
+ id: DbColumnDefinition;
66
+ /** Timestamp when the record was created */
67
+ created_at: DbColumnDefinition;
68
+ /** ID of the user who created the record */
69
+ created_by: DbColumnDefinition;
70
+ }
71
+
72
+ /**
73
+ * Complete database table schema definition.
74
+ * Defines the structure, constraints, and relationships for a database table.
75
+ */
76
+ export interface DbTableDefinition {
77
+ /** Name of the database table */
78
+ table_name: string;
79
+ /** Description of the table's purpose and usage */
80
+ description: string;
81
+ /** Permissions for the table */
82
+ permissions: {
83
+ user: DbPermissionDefinition,
84
+ moderator?: DbPermissionDefinition,
85
+ // maintainer?: DbPermissionDefinition,
86
+ };
87
+ /** Column definitions for the table */
88
+ columns: {
89
+ [column_name: string]: DbColumnDefinition;
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Permission definition for a database table.
95
+ * NONE means the action is not allowed.
96
+ * OWN means only do the action on your own records.
97
+ * ALL means do the action on all records.
98
+ *
99
+ * Defines the permissions for a database table.
100
+ */
101
+ export type DbPermission = "NONE" | "OWN" | "ALL";
102
+
103
+ /**
104
+ * Permission definition for a database table.
105
+ * Defines the permissions for a database table.
106
+ */
107
+ export interface DbPermissionDefinition {
108
+ read: DbPermission;
109
+ insert: DbPermission;
110
+ update: DbPermission;
111
+ delete: DbPermission;
112
+ }
113
+
114
+ /**
115
+ * Full table definition that includes automatically generated fields.
116
+ */
117
+ export type FullTable<T extends Record<string, DbColumnDefinition>> = T & BaseTableStructure;
@@ -16,7 +16,7 @@ interface Props {
16
16
 
17
17
  export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartConversation }: Props) {
18
18
  const [oralCommunication, setOralCommunication] = React.useState(true);
19
- const { llm, event } = usePlugin();
19
+ const { ai: llm, event } = usePlugin();
20
20
  const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
21
21
  const { messages, append, isLoading, setMessages } = useChat();
22
22
 
@@ -50,16 +50,16 @@ export function AssistantChat({ avatarImageUrl, voiceId, onComplete, autoStartCo
50
50
 
51
51
  useEffect(() => {
52
52
  console.log("lastMessage", lastMessage);
53
- const toolInvocations = lastMessage?.toolInvocations;
53
+ const toolInvocations = lastMessage?.toolCalls;
54
54
  if (toolInvocations && toolInvocations.length > 0) {
55
55
  console.log("toolInvocations", toolInvocations);
56
56
  onComplete(toolInvocations[0].args);
57
57
  }
58
58
  }, [lastMessage]);
59
59
 
60
- if (lastMessage?.toolInvocations && lastMessage.toolInvocations.length > 0) {
60
+ if (lastMessage?.toolCalls && lastMessage.toolCalls.length > 0) {
61
61
  console.log("lastMessage test2", lastMessage);
62
- const args = lastMessage.toolInvocations[0].args;
62
+ const args = lastMessage.toolCalls[0].args;
63
63
 
64
64
  const success = args.explanationUnderstood === "TRUE" || args.studentKnowsTopic === "TRUE";
65
65
 
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
2
2
  import { VoiceRecorder } from './EmbeddedAssistent/VoiceRecoder';
3
3
  import { MessageSender } from './EmbeddedAssistent/TTS/MessageSender';
4
4
  import { CircleAudioAvatar } from './EmbeddedAssistent/CircleAudioAvatar';
5
- import { Tool } from '../../controller/AIController';
5
+ import { Tool } from '../../fromRimori/PluginTypes';
6
6
  import { useChat } from '../../hooks/UseChatHook';
7
7
  import { usePlugin } from '../../components';
8
8
  import { getFirstMessages } from './utils';
@@ -16,13 +16,23 @@ interface Props {
16
16
  isDarkTheme?: boolean;
17
17
  children?: React.ReactNode;
18
18
  autoStartConversation?: FirstMessages;
19
+ className?: string;
19
20
  }
20
21
 
21
- export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversation, children, isDarkTheme = false, circleSize = "300px" }: Props) {
22
- const { llm, event } = usePlugin();
22
+ export function Avatar({
23
+ avatarImageUrl,
24
+ voiceId,
25
+ agentTools,
26
+ autoStartConversation,
27
+ children,
28
+ isDarkTheme = false,
29
+ circleSize = "300px",
30
+ className
31
+ }: Props) {
32
+ const { ai, event } = usePlugin();
23
33
  const [agentReplying, setAgentReplying] = useState(false);
24
34
  const [isProcessingMessage, setIsProcessingMessage] = useState(false);
25
- const sender = useMemo(() => new MessageSender(llm.getVoice, voiceId), []);
35
+ const sender = useMemo(() => new MessageSender(ai.getVoice, voiceId), [voiceId]);
26
36
  const { messages, append, isLoading, lastMessage, setMessages } = useChat(agentTools);
27
37
 
28
38
  useEffect(() => {
@@ -48,16 +58,21 @@ export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversat
48
58
  } else if (autoStartConversation.userMessage) {
49
59
  append([{ role: 'user', content: autoStartConversation.userMessage, id: messages.length.toString() }]);
50
60
  }
51
- }, []);
61
+ }, [autoStartConversation]);
52
62
 
53
63
  useEffect(() => {
54
64
  if (lastMessage?.role === 'assistant') {
55
65
  sender.handleNewText(lastMessage.content, isLoading);
66
+ if (lastMessage.toolCalls) {
67
+ console.log("unlocking mic",lastMessage)
68
+ setAgentReplying(false);
69
+ setIsProcessingMessage(false);
70
+ }
56
71
  }
57
72
  }, [lastMessage, isLoading]);
58
73
 
59
74
  return (
60
- <div className='pb-8'>
75
+ <div className={`md:pb-8 ${className || ''}`}>
61
76
  <CircleAudioAvatar
62
77
  width={circleSize}
63
78
  className='mx-auto'
@@ -65,9 +80,11 @@ export function Avatar({ avatarImageUrl, voiceId, agentTools, autoStartConversat
65
80
  isDarkTheme={isDarkTheme} />
66
81
  {children}
67
82
  <VoiceRecorder
68
- iconSize='300'
83
+ iconSize='30'
84
+ className='w-16 h-16 shadow-lg rounded-full bg-gray-400 dark:bg-gray-800'
69
85
  disabled={agentReplying}
70
86
  loading={isProcessingMessage}
87
+ enablePushToTalk={true}
71
88
  onVoiceRecorded={(message) => {
72
89
  setAgentReplying(true);
73
90
  append([{ role: 'user', content: "Message(" + Math.floor((messages.length + 1) / 2) + "): " + message, id: messages.length.toString() }]);
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { EventBus, EventBusMessage } from '../../../plugin/fromRimori/EventBus';
2
+ import { EventBus, EventBusMessage } from '../../../fromRimori/EventBus';
3
3
 
4
4
  interface CircleAudioAvatarProps {
5
5
  width?: string;
@@ -7,16 +7,18 @@ interface Props {
7
7
  className?: string;
8
8
  disabled?: boolean;
9
9
  loading?: boolean;
10
+ enablePushToTalk?: boolean;
10
11
  onRecordingStatusChange: (running: boolean) => void;
11
12
  onVoiceRecorded: (message: string) => void;
12
13
  }
13
14
 
14
- export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange }: Props, ref) => {
15
+ export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className, disabled, loading, onRecordingStatusChange, enablePushToTalk = false }: Props, ref) => {
15
16
  const [isRecording, setIsRecording] = useState(false);
17
+ const [internalIsProcessing, setInternalIsProcessing] = useState(false);
16
18
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
17
19
  const audioChunksRef = useRef<Blob[]>([]);
18
20
  const mediaStreamRef = useRef<MediaStream | null>(null);
19
- const { llm } = usePlugin();
21
+ const { ai: llm } = usePlugin();
20
22
 
21
23
  // Ref for latest onVoiceRecorded callback
22
24
  const onVoiceRecordedRef = useRef(onVoiceRecorded);
@@ -38,7 +40,11 @@ export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className,
38
40
  const audioBlob = new Blob(audioChunksRef.current);
39
41
  audioChunksRef.current = [];
40
42
 
41
- onVoiceRecordedRef.current(await llm.getTextFromVoice(audioBlob));
43
+
44
+ setInternalIsProcessing(true);
45
+ const text = await llm.getTextFromVoice(audioBlob);
46
+ setInternalIsProcessing(false);
47
+ onVoiceRecordedRef.current(text);
42
48
  };
43
49
 
44
50
  mediaRecorder.start();
@@ -67,6 +73,8 @@ export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className,
67
73
  const spacePressedRef = useRef(false);
68
74
 
69
75
  useEffect(() => {
76
+ if (!enablePushToTalk) return;
77
+
70
78
  const handleKeyDown = async (event: KeyboardEvent) => {
71
79
  if (event.code === 'Space' && !spacePressedRef.current) {
72
80
  spacePressedRef.current = true;
@@ -85,14 +93,14 @@ export const VoiceRecorder = forwardRef(({ onVoiceRecorded, iconSize, className,
85
93
  window.removeEventListener('keydown', handleKeyDown);
86
94
  window.removeEventListener('keyup', handleKeyUp);
87
95
  };
88
- }, []);
96
+ }, [enablePushToTalk]);
89
97
 
90
98
  return (
91
- <button className={"w-16 h-16 flex text-4xl shadow-lg flex-row justify-center items-center rounded-full mx-auto bg-gray-400 dark:bg-gray-800 pl-[6px] disabled:opacity-50 " + className}
99
+ <button className={"flex flex-row justify-center items-center rounded-full mx-auto disabled:opacity-50 " + className}
92
100
  onClick={isRecording ? stopRecording : startRecording}
93
- disabled={disabled || loading}>
94
- {loading ? <FaSpinner className="animate-spin mr-[6px]" /> :
95
- <FaMicrophone size={iconSize} className={"h-7 w-7 mr-2 " + (isRecording ? "text-red-600" : "")} />
101
+ disabled={disabled || loading || internalIsProcessing}>
102
+ {loading || internalIsProcessing ? <FaSpinner className="animate-spin" /> :
103
+ <FaMicrophone size={iconSize} className={(isRecording ? "text-red-600" : "")} />
96
104
  }
97
105
  </button>
98
106
  );
@@ -17,7 +17,5 @@ export function getFirstMessages(instructions: FirstMessages): any[] {
17
17
  messages.push({ id: '3', role: 'assistant', content: instructions.assistantMessage });
18
18
  }
19
19
 
20
- console.log("getFirstMessages", messages);
21
-
22
20
  return messages;
23
21
  }
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
2
2
  import { FaPlayCircle, FaStopCircle } from "react-icons/fa";
3
3
  import { usePlugin } from "../../providers/PluginProvider";
4
4
  import { Spinner } from '../Spinner';
5
- import { EventBus } from '../../plugin/fromRimori/EventBus';
5
+ import { EventBus } from '../../fromRimori/EventBus';
6
6
 
7
7
  type AudioPlayerProps = {
8
8
  text: string;
@@ -34,7 +34,7 @@ export const AudioPlayer: React.FC<AudioPlayerProps> = ({
34
34
  const [speed, setSpeed] = useState(initialSpeed);
35
35
  const [isPlaying, setIsPlaying] = useState(false);
36
36
  const [isLoading, setIsLoading] = useState(false);
37
- const { llm } = usePlugin();
37
+ const { ai } = usePlugin();
38
38
 
39
39
  useEffect(() => {
40
40
  if (!playListenerEvent) return;
@@ -52,7 +52,7 @@ export const AudioPlayer: React.FC<AudioPlayerProps> = ({
52
52
  const generateAudio = async () => {
53
53
  setIsLoading(true);
54
54
 
55
- const blob = await llm.getVoice(text, voice || (language ? "aws_default" : "openai_alloy"), 1, language);
55
+ const blob = await ai.getVoice(text, voice || (language ? "aws_default" : "openai_alloy"), 1, language);
56
56
  setAudioUrl(URL.createObjectURL(blob));
57
57
  setIsLoading(false);
58
58
  };
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect, useRef } from "react";
2
- import { EventBus } from "../../plugin/fromRimori/EventBus";
2
+ import { EventBus } from "../../fromRimori/EventBus";
3
3
  import { RimoriClient } from "../../plugin/RimoriClient";
4
- import { MenuEntry } from "../../plugin/fromRimori/PluginTypes";
4
+ import { MenuEntry } from "../../fromRimori/PluginTypes";
5
5
 
6
6
  export interface Position {
7
7
  x: number,
@@ -105,7 +105,7 @@ const ContextMenu = ({ client }: { client: RimoriClient }) => {
105
105
  <MenuEntryItem key={index} icon={action.icon} text={action.text} onClick={() => {
106
106
  setIsOpen(false);
107
107
  window.getSelection()?.removeAllRanges();
108
- client.event.emitSidebarAction(action.pluginId, action.actionKey, position.text);
108
+ client.event.emitSidebarAction(action.plugin_id, action.action_key, position.text);
109
109
  }} />
110
110
  ))}
111
111
  </div>
package/src/components.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  // React components and hooks exports
2
- export * from "./components/MarkdownEditor";
2
+ export * from "./components/ai/Assistant";
3
+ export * from "./components/ai/Avatar";
4
+ export * from "./components/ai/EmbeddedAssistent/VoiceRecoder";
5
+ export * from "./components/audio/Playbutton";
3
6
  export * from "./components/CRUDModal";
7
+ export * from "./components/MarkdownEditor";
4
8
  export * from "./components/Spinner";
5
- export * from "./components/audio/Playbutton";
6
9
  export * from "./hooks/UseChatHook";
7
10
  export * from "./plugin/ThemeSetter";
8
- export * from "./providers/PluginProvider";
9
- export * from "./components/ai/Avatar";
10
- export * from "./components/ai/Assistant";
11
- export * from "./types/Actions";
11
+ export * from "./providers/PluginProvider";