@solidstarters/solid-core 1.2.136 → 1.2.138

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 (88) hide show
  1. package/dist/controllers/ai-interaction.controller.d.ts +1 -0
  2. package/dist/controllers/ai-interaction.controller.d.ts.map +1 -1
  3. package/dist/controllers/ai-interaction.controller.js +12 -0
  4. package/dist/controllers/ai-interaction.controller.js.map +1 -1
  5. package/dist/dtos/create-dashboard-question.dto.d.ts.map +1 -1
  6. package/dist/dtos/create-dashboard-question.dto.js.map +1 -1
  7. package/dist/dtos/create-dashboard.dto.d.ts +2 -0
  8. package/dist/dtos/create-dashboard.dto.d.ts.map +1 -1
  9. package/dist/dtos/create-dashboard.dto.js +13 -1
  10. package/dist/dtos/create-dashboard.dto.js.map +1 -1
  11. package/dist/dtos/update-dashboard-question.dto.d.ts.map +1 -1
  12. package/dist/dtos/update-dashboard-question.dto.js.map +1 -1
  13. package/dist/dtos/update-dashboard.dto.d.ts +2 -0
  14. package/dist/dtos/update-dashboard.dto.d.ts.map +1 -1
  15. package/dist/dtos/update-dashboard.dto.js +13 -1
  16. package/dist/dtos/update-dashboard.dto.js.map +1 -1
  17. package/dist/entities/dashboard-question.entity.d.ts.map +1 -1
  18. package/dist/entities/dashboard-question.entity.js.map +1 -1
  19. package/dist/entities/dashboard.entity.d.ts +2 -0
  20. package/dist/entities/dashboard.entity.d.ts.map +1 -1
  21. package/dist/entities/dashboard.entity.js +9 -1
  22. package/dist/entities/dashboard.entity.js.map +1 -1
  23. package/dist/helpers/module.helper.d.ts.map +1 -1
  24. package/dist/helpers/module.helper.js +9 -2
  25. package/dist/helpers/module.helper.js.map +1 -1
  26. package/dist/interfaces.d.ts +5 -0
  27. package/dist/interfaces.d.ts.map +1 -1
  28. package/dist/interfaces.js.map +1 -1
  29. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
  30. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +23 -3
  31. package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
  32. package/dist/seeders/seed-data/solid-core-metadata.json +45 -0
  33. package/dist/services/ai-interaction.service.d.ts +4 -1
  34. package/dist/services/ai-interaction.service.d.ts.map +1 -1
  35. package/dist/services/ai-interaction.service.js +34 -4
  36. package/dist/services/ai-interaction.service.js.map +1 -1
  37. package/dist/services/dashboard.service.d.ts.map +1 -1
  38. package/dist/services/dashboard.service.js +7 -2
  39. package/dist/services/dashboard.service.js.map +1 -1
  40. package/dist/services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service.d.ts +9 -0
  41. package/dist/services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service.d.ts.map +1 -0
  42. package/dist/services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service.js +40 -0
  43. package/dist/services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service.js.map +1 -0
  44. package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.d.ts +14 -0
  45. package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.d.ts.map +1 -0
  46. package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js +48 -0
  47. package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js.map +1 -0
  48. package/dist/services/module-metadata.service.d.ts.map +1 -1
  49. package/dist/services/module-metadata.service.js.map +1 -1
  50. package/dist/services/scheduled-jobs/scheduled-job.interface.d.ts +1 -1
  51. package/dist/services/scheduled-jobs/scheduled-job.interface.d.ts.map +1 -1
  52. package/dist/services/scheduled-jobs/scheduled-job.interface.js.map +1 -1
  53. package/dist/services/scheduled-jobs/scheduler.service.js +1 -6
  54. package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
  55. package/dist/services/sql-expression-resolver.service.d.ts +3 -0
  56. package/dist/services/sql-expression-resolver.service.d.ts.map +1 -1
  57. package/dist/services/sql-expression-resolver.service.js +18 -3
  58. package/dist/services/sql-expression-resolver.service.js.map +1 -1
  59. package/dist/solid-core.module.d.ts.map +1 -1
  60. package/dist/solid-core.module.js +4 -0
  61. package/dist/solid-core.module.js.map +1 -1
  62. package/dist/subscribers/dashboard.subscriber.d.ts +1 -0
  63. package/dist/subscribers/dashboard.subscriber.d.ts.map +1 -1
  64. package/dist/subscribers/dashboard.subscriber.js +17 -2
  65. package/dist/subscribers/dashboard.subscriber.js.map +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +1 -1
  68. package/src/controllers/ai-interaction.controller.ts +6 -0
  69. package/src/dtos/create-dashboard-question.dto.ts +4 -5
  70. package/src/dtos/create-dashboard.dto.ts +8 -0
  71. package/src/dtos/update-dashboard-question.dto.ts +4 -5
  72. package/src/dtos/update-dashboard.dto.ts +8 -0
  73. package/src/entities/dashboard-question.entity.ts +2 -3
  74. package/src/entities/dashboard.entity.ts +4 -0
  75. package/src/helpers/module.helper.ts +33 -20
  76. package/src/interfaces.ts +6 -0
  77. package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +29 -11
  78. package/src/seeders/seed-data/solid-core-metadata.json +45 -0
  79. package/src/services/ai-interaction.service.ts +50 -2
  80. package/src/services/dashboard.service.ts +7 -2
  81. package/src/services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service.ts +36 -0
  82. package/src/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.ts +55 -0
  83. package/src/services/module-metadata.service.ts +0 -5
  84. package/src/services/scheduled-jobs/scheduled-job.interface.ts +1 -1
  85. package/src/services/scheduled-jobs/scheduler.service.ts +6 -6
  86. package/src/services/sql-expression-resolver.service.ts +16 -2
  87. package/src/solid-core.module.ts +5 -0
  88. package/src/subscribers/dashboard.subscriber.ts +24 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.136",
3
+ "version": "1.2.138",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -96,6 +96,12 @@ export class AiInteractionController {
96
96
  return this.service.triggerMcpClientJob(dto.prompt);
97
97
  }
98
98
 
99
+ @ApiBearerAuth("jwt")
100
+ @Post(':id/apply-solid-ai-interaction')
101
+ async applySolidAiInteraction(@Param('id') id: number) {
102
+ return this.service.applySolidAiInteraction(+id);
103
+ }
104
+
99
105
  @ApiBearerAuth("jwt")
100
106
  @Post('/run-mcp-prompt')
101
107
  async runMcpPrompt(@Body() dto: InvokeAiPromptDto) {
@@ -55,9 +55,8 @@ export class CreateDashboardQuestionDto {
55
55
  @IsString()
56
56
  @ApiProperty({ description: "This is the SQL query to fetch the KPI value for the question" })
57
57
  kpiSql: string;
58
-
59
- @IsOptional()
60
- @IsInt()
61
- @ApiProperty()
62
- sequenceNumber: number;
58
+ @IsOptional()
59
+ @IsInt()
60
+ @ApiProperty()
61
+ sequenceNumber: number;
63
62
  }
@@ -50,4 +50,12 @@ export class CreateDashboardDto {
50
50
  @IsOptional()
51
51
  @ApiProperty()
52
52
  moduleUserKey: string;
53
+ @IsOptional()
54
+ @IsString()
55
+ @ApiProperty()
56
+ displayName: string;
57
+ @IsOptional()
58
+ @IsString()
59
+ @ApiProperty({ description: "This is a description of the dashboard configuration, providing context and details about the dashboard." })
60
+ description: string;
53
61
  }
@@ -60,9 +60,8 @@ export class UpdateDashboardQuestionDto {
60
60
  @IsString()
61
61
  @ApiProperty({ description: "This is the SQL query to fetch the KPI value for the question" })
62
62
  kpiSql: string;
63
-
64
- @IsOptional()
65
- @IsInt()
66
- @ApiProperty()
67
- sequenceNumber: number;
63
+ @IsOptional()
64
+ @IsInt()
65
+ @ApiProperty()
66
+ sequenceNumber: number;
68
67
  }
@@ -54,4 +54,12 @@ export class UpdateDashboardDto {
54
54
  @IsOptional()
55
55
  @ApiProperty()
56
56
  moduleUserKey: string;
57
+ @IsOptional()
58
+ @IsString()
59
+ @ApiProperty()
60
+ displayName: string;
61
+ @IsOptional()
62
+ @IsString()
63
+ @ApiProperty({ description: "This is a description of the dashboard configuration, providing context and details about the dashboard." })
64
+ description: string;
57
65
  }
@@ -27,7 +27,6 @@ export class DashboardQuestion extends CommonEntity {
27
27
  labelSql: string;
28
28
  @Column({ type: "text", nullable: true })
29
29
  kpiSql: string;
30
-
31
- @Column({ type: "integer", nullable: true })
32
- sequenceNumber: number;
30
+ @Column({ type: "integer", nullable: true })
31
+ sequenceNumber: number;
33
32
  }
@@ -18,4 +18,8 @@ export class Dashboard extends CommonEntity {
18
18
  @ManyToOne(() => ModuleMetadata, { onDelete: "CASCADE", nullable: false })
19
19
  @JoinColumn()
20
20
  module: ModuleMetadata;
21
+ @Column({ type: "varchar", nullable: true })
22
+ displayName: string;
23
+ @Column({ type: "text", nullable: true })
24
+ description: string;
21
25
  }
@@ -3,23 +3,36 @@ import * as path from 'path'; // To handle file paths
3
3
 
4
4
 
5
5
  export const getDynamicModuleNames = (): string[] => {
6
- const dynamicModulesToExclude = process.env.SOLID_DYNAMIC_MODULES_TO_EXCLUDE?.split(',') || [];
7
-
8
- // Adjust if 'src' is in a different location
9
- const srcPath = path.join(process.cwd(), 'src');
10
- const coreModuleNames = getCoreModuleNames();
11
- const allExcludedModules = [...new Set([...coreModuleNames, ...dynamicModulesToExclude])];
12
-
13
- const directories = fs.readdirSync(srcPath, { withFileTypes: true });
14
- const enabledModules = directories
15
- .filter(d => d.isDirectory() && !allExcludedModules.includes(d.name))
16
- .map(d => d.name);
17
-
18
- console.log(`Enabled dynamic modules:`, enabledModules);
19
- return enabledModules;
20
- }
21
-
22
- export const getCoreModuleNames = (): string[] => {
23
- // return ['iam', 'common', 'queues', 'app-builder'];
24
- return ['solid-core'];
25
- }
6
+ const dynamicModulesToExclude = process.env.SOLID_DYNAMIC_MODULES_TO_EXCLUDE?.split(',') || [];
7
+
8
+ // Adjust if 'src' is in a different location
9
+ const srcPath = path.join(process.cwd(), 'src');
10
+ const coreModuleNames = getCoreModuleNames();
11
+ const allExcludedModules = [...new Set([...coreModuleNames, ...dynamicModulesToExclude])];
12
+
13
+ const directories = fs.readdirSync(srcPath, { withFileTypes: true });
14
+ // const enabledModules = directories
15
+ // .filter(d => d.isDirectory() && !allExcludedModules.includes(d.name))
16
+ // .map(d => d.name);
17
+
18
+ const enabledModules = directories
19
+ .filter(dirent => {
20
+ const isValidDirectory = dirent.isDirectory() && !allExcludedModules.includes(dirent.name);
21
+
22
+ if (!isValidDirectory) return false;
23
+
24
+ const fullPath = path.join(srcPath, dirent.name);
25
+ const files = fs.readdirSync(fullPath);
26
+ // skip if empty directory
27
+ return files.length > 0;
28
+ })
29
+ .map(dirent => dirent.name);
30
+
31
+ console.log(`Enabled dynamic modules:`, enabledModules);
32
+ return enabledModules;
33
+ }
34
+
35
+ export const getCoreModuleNames = (): string[] => {
36
+ // return ['iam', 'common', 'queues', 'app-builder'];
37
+ return ['solid-core'];
38
+ }
package/src/interfaces.ts CHANGED
@@ -13,6 +13,7 @@ import { DashboardQuestion } from './entities/dashboard-question.entity';
13
13
  import { ComputedFieldMetadata } from './helpers/solid-registry';
14
14
  import { SqlExpression } from './services/question-data-providers/chartjs-sql-data-provider.service';
15
15
  import { CreateDashboardDto } from './dtos/create-dashboard.dto';
16
+ import { AiInteraction } from './entities/ai-interaction.entity';
16
17
 
17
18
  export interface FieldCrudManager {
18
19
  // fieldMetadata: FieldMetadata;
@@ -77,6 +78,7 @@ export interface McpResponse {
77
78
  duration_ms?: number;
78
79
  errors?: string[];
79
80
  trace?: string[];
81
+ content_type?: string;
80
82
  }
81
83
 
82
84
  export interface ISelectionProviderContext {
@@ -101,6 +103,10 @@ export interface ISelectionProvider<T extends ISelectionProviderContext> {
101
103
  export interface IDashboardVariableSelectionProvider<T extends ISelectionProviderContext> extends ISelectionProvider<T> {
102
104
  }
103
105
 
106
+ export interface IMcpToolResponseHandler {
107
+ apply(aiInteraction: AiInteraction);
108
+ }
109
+
104
110
  export interface IDashboardQuestionDataProvider<TContext, TData> {
105
111
  help(): string;
106
112
 
@@ -34,36 +34,54 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
34
34
  const aiInteraction = await this.aiInteractionService.findOne(codeGnerationOptions.aiInteractionId, {
35
35
  populate: ['user']
36
36
  });
37
+ if (!aiInteraction) {
38
+ const m = `Unable to identified the aiInteraction entry that triggered this job... using id: ${codeGnerationOptions.aiInteractionId}`
39
+ this.triggerMcpClientSubscriberLogger.log(m);
40
+ throw new Error(m);
41
+ }
37
42
 
38
43
  // The message contains the users prompt.
39
44
  const prompt = aiInteraction.message;
40
45
 
41
46
  // Use this to invoke our mcp client
42
47
  const aiResponse = await this.aiInteractionService.runMcpPrompt(prompt);
43
- let nestedResponse = aiResponse.response.trim();
48
+ this.triggerMcpClientSubscriberLogger.log(`aiResponse: `);
49
+ this.triggerMcpClientSubscriberLogger.log(JSON.stringify(aiResponse));
50
+
44
51
  if (!aiResponse.success) {
45
- // update the job entry with success... raising an error will lead the job to be marked as failed...
46
- throw new Error(aiResponse.errors.join(','));
52
+ this.triggerMcpClientSubscriberLogger.log(`Gen ai has returned with a false status code`);
53
+
54
+ const errorsStr = aiResponse.errors.join('; ');
55
+
56
+ await this.aiInteractionService.create({
57
+ userId: aiInteraction.user.id,
58
+ threadId: aiInteraction.threadId,
59
+ role: 'gen-ai',
60
+ message: '-',
61
+ contentType: aiResponse.content_type,
62
+ errorMessage: errorsStr,
63
+ modelUsed: aiResponse.model,
64
+ responseTimeMs: aiResponse.duration_ms,
65
+ metadata: JSON.stringify(aiResponse)
66
+ });
67
+
68
+ // update the job entry with failure... raising an error will lead the job to be marked as failed...
69
+ throw new Error(errorsStr);
47
70
  }
48
71
  else {
49
- // TODO: create a new entry not update...
50
- // const updatedDto = {
51
- // ...aiInteraction,
52
- // message: nestedResponse,
53
- // }
54
- // await this.aiInteractionService.update(codeGnerationOptions.aiInteractionId, updatedDto);
72
+ let nestedResponse = aiResponse.response.trim();
55
73
 
56
74
  await this.aiInteractionService.create({
57
75
  userId: aiInteraction.user.id,
58
76
  threadId: aiInteraction.threadId,
59
77
  role: 'gen-ai',
60
78
  message: nestedResponse,
61
- contentType: '',
79
+ contentType: aiResponse.content_type,
62
80
  errorMessage: '',
63
81
  modelUsed: aiResponse.model,
64
82
  responseTimeMs: aiResponse.duration_ms,
65
83
  metadata: JSON.stringify(aiResponse)
66
- })
84
+ });
67
85
  }
68
86
 
69
87
  return aiResponse;
@@ -4308,6 +4308,33 @@
4308
4308
  "isSystem": true,
4309
4309
  "isUserKey": true
4310
4310
  },
4311
+ {
4312
+ "name": "displayName",
4313
+ "displayName": "Display Name",
4314
+ "type": "shortText",
4315
+ "ormType": "varchar",
4316
+ "length": 256,
4317
+ "required": false,
4318
+ "unique": false,
4319
+ "index": false,
4320
+ "private": false,
4321
+ "encrypt": false,
4322
+ "isSystem": true,
4323
+ "isUserKey": false
4324
+ },
4325
+ {
4326
+ "name": "description",
4327
+ "displayName": "Description",
4328
+ "type": "longText",
4329
+ "ormType": "text",
4330
+ "required": false,
4331
+ "unique": false,
4332
+ "index": false,
4333
+ "private": false,
4334
+ "encrypt": false,
4335
+ "isSystem": true,
4336
+ "description": "This is a description of the dashboard configuration, providing context and details about the dashboard."
4337
+ },
4311
4338
  {
4312
4339
  "name": "layoutJson",
4313
4340
  "displayName": "Layout Json",
@@ -11075,6 +11102,12 @@
11075
11102
  "name": "name"
11076
11103
  }
11077
11104
  },
11105
+ {
11106
+ "type": "field",
11107
+ "attrs": {
11108
+ "name": "displayName"
11109
+ }
11110
+ },
11078
11111
  {
11079
11112
  "type": "field",
11080
11113
  "attrs": {
@@ -11138,6 +11171,18 @@
11138
11171
  "name": "name"
11139
11172
  }
11140
11173
  },
11174
+ {
11175
+ "type": "field",
11176
+ "attrs": {
11177
+ "name": "displayName"
11178
+ }
11179
+ },
11180
+ {
11181
+ "type": "field",
11182
+ "attrs": {
11183
+ "name": "description"
11184
+ }
11185
+ },
11141
11186
  {
11142
11187
  "type": "field",
11143
11188
  "attrs": {
@@ -16,6 +16,7 @@ import { McpResponse, TriggerMcpClientOptions } from 'src/interfaces';
16
16
  import { PublisherFactory } from './queues/publisher-factory.service';
17
17
  import { RequestContextService } from './request-context.service';
18
18
  import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
19
+ import { McpToolResponseHandlerFactory } from './mcp-tool-response-handlers/mcp-tool-response-handler-factory.service';
19
20
 
20
21
  @Injectable()
21
22
  export class AiInteractionService extends CRUDService<AiInteraction> {
@@ -35,6 +36,7 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
35
36
  readonly moduleRef: ModuleRef,
36
37
  readonly publisherFactory: PublisherFactory<TriggerMcpClientOptions>,
37
38
  readonly requestContextService: RequestContextService,
39
+ readonly mcpToolResponseHandlerFactory: McpToolResponseHandlerFactory,
38
40
 
39
41
  ) {
40
42
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);
@@ -101,9 +103,9 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
101
103
  // TODO: Refactor to use the command.service.ts instead...
102
104
  return new Promise((resolve, reject) => {
103
105
  this.logger.log(`Attempting to run command:`)
104
- this.logger.log(`${pythonExecutable} ${mcpClient} ${prompt}`);
106
+ this.logger.log(`${pythonExecutable} ${mcpClient} "${prompt}"`);
105
107
 
106
- const python = spawn(pythonExecutable, [mcpClient, prompt]);
108
+ const python = spawn(pythonExecutable, [mcpClient, `"${prompt}"`]);
107
109
 
108
110
  let stdout = '';
109
111
  let stderr = '';
@@ -160,4 +162,50 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
160
162
  return response;
161
163
  }
162
164
 
165
+ async applySolidAiInteraction(id: number) {
166
+ // Fetch the aiInteraction
167
+ const aiInteraction = await this.findOne(id, {
168
+ populate: ['user']
169
+ });
170
+ if (!aiInteraction) {
171
+ const m = `Unable to identified the aiInteraction entry that triggered this job... using id: ${id}`
172
+
173
+ // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....
174
+ throw new Error(m);
175
+ }
176
+
177
+ // TODO: Validation: Check if JSON.parse(metadata).tools_invoked starts with solid_
178
+ let metadata = {};
179
+ try {
180
+ metadata = JSON.parse(aiInteraction.metadata);
181
+ }
182
+ catch (e) {
183
+ // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....
184
+ throw new Error(e);
185
+ }
186
+
187
+ const toolsInvoked = metadata['tools_invoked'];
188
+ if (!toolsInvoked) {
189
+ // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....
190
+ throw new Error('Unable to resolve a solid_ command that was used to come up with this response.');
191
+ }
192
+
193
+ // TODO: OPTIMISATION for chained tool invocation, for now we are assuming only 1 tool was used.
194
+ const toolInvoked = toolsInvoked[0];
195
+
196
+ // TODO: use the toolInvoked to identify a service using some convention.
197
+ // TODO: Eg. if toolInvoked is solid_create_module <> SolidCreateModuleMcpToolHandler ... create a factory class to do this mapping and identify the relevant provider.
198
+ const mcpToolHandler = this.mcpToolResponseHandlerFactory.getInstance(toolInvoked);
199
+ if (!mcpToolHandler) {
200
+ // TODO: RESPONSE SHAPE ALERT Check if we want to control the shape of the response....
201
+ throw new Error('Unable to resolve a mcp tool handler.');
202
+ }
203
+
204
+ const handlerApplicationResponse = await mcpToolHandler.apply(aiInteraction);
205
+
206
+ // TODO: This provider to implement an interface - IMcpToolResponseHandler ... apply(aiInteraction: AiInteraction)
207
+ // throw new Error('Method not implemented.');
208
+
209
+ return handlerApplicationResponse;
210
+ }
163
211
  }
@@ -129,10 +129,15 @@ export class DashboardService extends CRUDService<Dashboard> {
129
129
  }
130
130
 
131
131
  private async writeToConfig(metaData: any, dashboard: Dashboard, filePath: string) {
132
- if (metaData.dashboards) {
132
+ if (metaData.dashboards && Array.isArray(metaData.dashboards)) {
133
133
  const dashboardIndex = metaData.dashboards?.findIndex((dashboardFromFile: { name: string; }) => dashboardFromFile.name === dashboard.name);
134
134
  const dto = await this.dashboardMapper.toDto(dashboard);
135
- metaData.dashboards[dashboardIndex] = dto;
135
+ if (dashboardIndex !== -1) {
136
+ metaData.dashboards[dashboardIndex] = dto;
137
+ }
138
+ else {
139
+ metaData.dashboards.push(dto);
140
+ }
136
141
  }
137
142
  else {
138
143
  const dashboards = [];
@@ -0,0 +1,36 @@
1
+ import { Logger } from '@nestjs/common';
2
+ import { Injectable } from '@nestjs/common';
3
+
4
+ import { QueueMessage, QueuePublisher } from 'src/interfaces/mq';
5
+ import { classify } from '@angular-devkit/core/src/utils/strings';
6
+ import { SolidIntrospectService } from '../solid-introspect.service';
7
+ import { IMcpToolResponseHandler } from 'src/interfaces';
8
+
9
+
10
+ @Injectable()
11
+ export class McpToolResponseHandlerFactory {
12
+ private readonly logger = new Logger(McpToolResponseHandlerFactory.name);
13
+
14
+ constructor(
15
+ private readonly solidIntrospectionService: SolidIntrospectService
16
+ ) {
17
+ }
18
+
19
+ getInstance(toolInvoked: string): IMcpToolResponseHandler {
20
+ toolInvoked = classify(toolInvoked);
21
+
22
+ let resolvedHandlerName = `${toolInvoked}McpToolResponseHandler`;
23
+
24
+ // Register all ISolidDatabaseModules implementations
25
+ let actualHandler = this.solidIntrospectionService.getProvider(resolvedHandlerName);
26
+ if (!actualHandler) {
27
+ throw new Error(`Unable to locate mcp tool handler with name ${resolvedHandlerName}`);
28
+ }
29
+
30
+ // type safe
31
+ const actualHandlerInstance: IMcpToolResponseHandler = actualHandler.instance;
32
+ this.logger.error(`Resolved mcp tool response handler with name ${actualHandler.name}`);
33
+
34
+ return actualHandlerInstance;
35
+ }
36
+ }
@@ -0,0 +1,55 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { IMcpToolResponseHandler } from "../../interfaces";
3
+ import { AiInteraction } from "src/entities/ai-interaction.entity";
4
+ import { ModuleMetadataService } from "../module-metadata.service";
5
+ import { CreateModuleMetadataDto } from "src/dtos/create-module-metadata.dto";
6
+ import { SolidRegistry } from "src/helpers/solid-registry";
7
+
8
+ @Injectable()
9
+ export class SolidCreateModuleMcpToolResponseHandler implements IMcpToolResponseHandler {
10
+
11
+ constructor(
12
+ private readonly moduleMetadataService: ModuleMetadataService,
13
+ private readonly solidRegistry: SolidRegistry,
14
+
15
+ ) {
16
+ }
17
+
18
+ async apply(aiInteraction: AiInteraction) {
19
+ const aiResponse = JSON.parse(aiInteraction.message);
20
+
21
+ const moduleMetadata = aiResponse['moduleMetadata'];
22
+
23
+ // TODO: Validate if another module with same name exists, if it does then raise an error...
24
+
25
+ const createDto: CreateModuleMetadataDto = {
26
+ defaultDataSource: 'default',
27
+ description: moduleMetadata['description'],
28
+ displayName: moduleMetadata['displayName'],
29
+ isSystem: false,
30
+ menuIconUrl: '',
31
+ models: [],
32
+ name: moduleMetadata['name'],
33
+ menuSequenceNumber: 1
34
+ }
35
+
36
+ // This creates the module-metadata.json file....
37
+ const moduleObj = await this.moduleMetadataService.create(createDto);
38
+
39
+ // const seeder = this.solidRegistry.getSeeders().filter((seeder) => seeder.name === 'ModuleMetadataSeederService').map((seeder) => seeder.instance).pop();
40
+
41
+ // Now we need to run solid seed & then solid refresh-model --name <module-name>
42
+ await this.moduleMetadataService.generateCode({ moduleId: moduleObj.id });
43
+
44
+ // solid seed ... this has to be run after reboot from the UI...
45
+ // await new Promise(resolve => setTimeout(resolve, 1000));
46
+ // await seeder.seed();
47
+
48
+ // TODO: decide on some shape to return hre...
49
+ return {
50
+ seedingRequired: true,
51
+ serverRebooting: true,
52
+ }
53
+ }
54
+
55
+ }
@@ -213,7 +213,6 @@ export class ModuleMetadataService {
213
213
  }
214
214
  }
215
215
 
216
-
217
216
  async updateInDB(manager: EntityManager, id: number, updateModuleMetadataDto: UpdateModuleMetadataDto, files: Express.Multer.File[] = []) {
218
217
 
219
218
  const module = await this.moduleMetadataRepo.preload({
@@ -286,7 +285,6 @@ export class ModuleMetadataService {
286
285
  }
287
286
  }
288
287
 
289
-
290
288
  async upsert(updateModuleMetadataDto: UpdateModuleMetadataDto) {
291
289
  this.logger.log(`Module Upsert called for : ${updateModuleMetadataDto.name}`);
292
290
  // First check if module already exists using name
@@ -308,9 +306,6 @@ export class ModuleMetadataService {
308
306
  }
309
307
  }
310
308
 
311
-
312
-
313
-
314
309
  async removeByName(name: string) {
315
310
  const entity = await this.findOneByUserKey(name);
316
311
  if (entity) {
@@ -1,5 +1,5 @@
1
1
  import { ScheduledJob } from "src/entities/scheduled-job.entity";
2
2
 
3
3
  export interface IScheduledJob {
4
- executeReminder(reminder: ScheduledJob): Promise<void>;
4
+ execute(job: ScheduledJob): Promise<void>;
5
5
  }
@@ -22,7 +22,7 @@ export class SchedulerServiceImpl implements ISchedulerService {
22
22
  async runScheduledJobs(): Promise<void> {
23
23
  const now = new Date();
24
24
 
25
- this.logger.log(`[${now.getTime()}]: scheduler service started run...`);
25
+ // this.logger.log(`[${now.getTime()}]: scheduler service started run...`);
26
26
  const dueJobs = await this.scheduledJobRepo.find({
27
27
  where: [
28
28
  {
@@ -37,7 +37,7 @@ export class SchedulerServiceImpl implements ISchedulerService {
37
37
  ],
38
38
  });
39
39
 
40
- this.logger.log(`[${now.getTime()}]: scheduler service identified ${dueJobs.length} jobs to run...`);
40
+ // this.logger.log(`[${now.getTime()}]: scheduler service identified ${dueJobs.length} jobs to run...`);
41
41
 
42
42
  for (const job of dueJobs) {
43
43
  this.logger.log(`[${now.getTime()}]: scheduler service attempting to run job ${job.job}`);
@@ -49,13 +49,13 @@ export class SchedulerServiceImpl implements ISchedulerService {
49
49
 
50
50
  const handler = this.solidRegistry.getScheduledJobProviderInstance(job.job);
51
51
  if (!handler) {
52
- this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);
52
+ // this.logger.warn(`[${now.getTime()}]: scheduler service skipping because job handler not found: ${job.job}`);
53
53
  continue;
54
54
  }
55
55
 
56
- this.logger.log(`[${now.getTime()}]: scheduler service about to run job ${job.job}`);
57
- await handler.executeReminder(job);
58
- this.logger.log(`[${now.getTime()}]: scheduler service finished running job ${job.job}`);
56
+ // this.logger.log(`[${now.getTime()}]: scheduler service about to run job ${job.job}`);
57
+ await handler.execute(job);
58
+ // this.logger.log(`[${now.getTime()}]: scheduler service finished running job ${job.job}`);
59
59
 
60
60
  job.isActive = true;
61
61
  job.lastRunAt = now;
@@ -1,5 +1,6 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { SqlExpression, SqlExpressionOperator } from "./question-data-providers/chartjs-sql-data-provider.service";
3
+ import { RequestContextService } from "./request-context.service";
3
4
 
4
5
  export interface SqlReplacementResult {
5
6
  rawSql: string;
@@ -8,10 +9,25 @@ export interface SqlReplacementResult {
8
9
 
9
10
  @Injectable()
10
11
  export class SqlExpressionResolverService {
12
+ constructor(private readonly requestContextService: RequestContextService) { }
11
13
  resolveSqlWithExpressions(sql: string, expressions: SqlExpression[]): SqlReplacementResult {
12
14
  const variableToColumnMap: Record<string, string> = {};
13
15
  const variablePattern = /{{\s*(\w+)\s*\[\s*([\w.]+)\s*\]\s*}}/g;
14
16
 
17
+ let paramIndex = 1;
18
+ const parameters: any[] = [];
19
+
20
+ // Handle sql expression tokens like {{$activeUserId}} in the SQL string
21
+ if (sql.includes('{{$activeUserId}}')) {
22
+ const activeUser = this.requestContextService.getActiveUser();
23
+ if (activeUser && activeUser.sub) {
24
+ // Replace custom placeholder with parameter placeholder ($1)
25
+ sql = sql.replace(/\{\{\$activeUserId\}\}/g, `$${paramIndex++}`);
26
+ // Add the active user ID to parameters
27
+ parameters.push(activeUser.sub);
28
+ }
29
+ }
30
+
15
31
  // --- Pass 1: extract variable -> column mappings ---
16
32
  let simplifiedSql = sql.replace(variablePattern, (_, variableName, columnName) => {
17
33
  variableToColumnMap[variableName] = columnName;
@@ -19,8 +35,6 @@ export class SqlExpressionResolverService {
19
35
  });
20
36
 
21
37
  // --- Pass 2: Replace each variable with positional fragment ---
22
- let paramIndex = 1;
23
- const parameters: any[] = [];
24
38
 
25
39
  for (const expr of expressions) {
26
40
  const column = variableToColumnMap[expr.variableName];
@@ -247,6 +247,8 @@ import { DashboardSubscriber } from './subscribers/dashboard.subscriber';
247
247
  import { SecurityRuleSubscriber } from './subscribers/security-rule.subscriber';
248
248
  import { ViewMetadataSubsciber } from './subscribers/view-metadata.subscriber';
249
249
  import { CRUDService } from './services/crud.service';
250
+ import { McpToolResponseHandlerFactory } from './services/mcp-tool-response-handlers/mcp-tool-response-handler-factory.service';
251
+ import { SolidCreateModuleMcpToolResponseHandler } from './services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service';
250
252
 
251
253
 
252
254
  @Global()
@@ -417,6 +419,9 @@ import { CRUDService } from './services/crud.service';
417
419
  SmsTemplateService,
418
420
  EmailTemplateService,
419
421
  PublisherFactory,
422
+
423
+ McpToolResponseHandlerFactory,
424
+ SolidCreateModuleMcpToolResponseHandler,
420
425
 
421
426
  TriggerMcpClientPublisherDatabase,
422
427
  TriggerMcpClientSubscriberDatabase,