Skip to content

Conversation

@Artuomka
Copy link
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings December 22, 2025 11:11
@Artuomka Artuomka enabled auto-merge December 22, 2025 11:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces AWS Bedrock integration for AI-powered automatic generation of table settings and widgets when connections are created. The implementation uses Amazon's Claude model through AWS Bedrock to analyze database schemas and generate optimal configuration for displaying tables in the admin panel.

Key Changes:

  • Adds AWS Bedrock Runtime client dependency (v3.954.0) and implements an AI service that generates table settings and widgets based on database structure
  • Replaces the manual widget creation logic with AI-generated configurations during connection creation (skipped in test environments)
  • Updates test expectations to reflect that automatic widgets are no longer created in test mode (changed from 1/2 widgets to 0)

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
yarn.lock Adds AWS SDK Bedrock Runtime and related dependencies for AWS AI service integration
backend/package.json Adds @aws-sdk/client-bedrock-runtime dependency
backend/src/entities/ai/amazon-bedrock/amazon-bedrock.ai.provider.ts New AI provider implementation using AWS Bedrock with Claude model for response generation
backend/src/entities/ai/amazon-bedrock/ai-provider.interface.ts Interface definition for AI providers
backend/src/entities/ai/ai.service.ts Core AI service that builds prompts, parses responses, and creates table settings entities
backend/src/entities/ai/ai.module.ts Updates module to include AI service, provider, and new use case with global scope
backend/src/entities/ai/user-ai-requests-v2.controller.ts Adds new GET endpoint for triggering AI-based settings/widgets creation
backend/src/entities/ai/use-cases/request-ai-settings-and-widgets-creation.use.case.ts New use case for handling AI settings and widgets creation requests
backend/src/entities/ai/ai-use-cases.interface.ts Adds interface for AI settings and widgets creation use case
backend/src/entities/ai/ai-data-entities/types/ai-module-types.ts Defines TableInformation type for AI processing
backend/src/entities/shared-jobs/shared-jobs.service.ts Adds scanDatabaseAndCreateSettingsAndWidgetsWithAI method that orchestrates AI-based configuration generation
backend/src/entities/connection/use-cases/create-connection.use.case.ts Switches from manual widget creation to AI-based approach during connection creation
backend/src/common/data-injection.tokens.ts Adds new use case token for dependency injection
backend/src/app.module.ts Imports AIModule at application level
backend/test/ava-tests/saas-tests/*.test.ts Updates test assertions to expect 0 widgets instead of 1 since AI creation is disabled in tests
backend/test/ava-tests/non-saas-tests/*.test.ts Updates test assertions to expect 0 widgets instead of 1 since AI creation is disabled in tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return responseText || 'No response generated.';
} catch (error) {
console.error('Error generating AI response:', error);
throw new Error('Failed to generate AI response.');
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling throws a generic Error with a hardcoded message that loses the original error details. This makes debugging AI provider issues difficult. Consider including the original error message or using a more descriptive error that includes context about what failed (e.g., model ID, region, or specific AWS error codes).

Suggested change
throw new Error('Failed to generate AI response.');
const originalMessage =
error instanceof Error ? error.message : typeof error === 'string' ? error : JSON.stringify(error);
throw new Error(
`Failed to generate AI response (modelId=${this.modelId}, region=${this.region}): ${originalMessage}`,
);

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +95
@Get('/ai/v2/setup/:connectionId')
public async requestAISettingsAndWidgetsCreation(
@SlugUuid('connectionId') connectionId: string,
@MasterPassword() masterPassword: string,
@UserId() userId: string,
): Promise<void> {
const connectionData = {
connectionId,
masterPwd: masterPassword,
cognitoUserName: userId,
};
return await this.requestAISettingsAndWidgetsCreationUseCase.execute(connectionData, InTransactionEnum.OFF);
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new GET endpoint '/ai/v2/setup/:connectionId' is missing the @UseGuards decorator. Other endpoints that access connection data use ConnectionReadGuard or similar guards to verify permissions. Without a guard, any authenticated user could potentially trigger AI processing for any connection they have access to, which may have cost and performance implications.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +77
const validationQueue = new PQueue({ concurrency: 4 });
const validatedSettings = await Promise.all(
normalizedSettings.map((setting) =>
validationQueue.add(async () => {
const validateSettingsDS = buildValidateTableSettingsDS(setting);
const errors = await dao.validateSettings(validateSettingsDS, setting.table_name, undefined);
if (errors.length > 0) {
console.error(`Validation errors for table "${setting.table_name}":`, errors);
return null;
}
return setting;
}),
),
);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When validation errors occur for a table's settings, only an error is logged to console and the setting is filtered out. However, there's no notification to the user or tracking of which tables failed validation. This could result in silent failures where some tables don't get AI-generated settings without any visible indication to the user. Consider tracking failed tables and returning this information, or at least capturing it in Sentry.

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +144
aiSettings.forEach((setting) => {
delete setting.id;
setting.connection_id = connection;
delete setting.table_widgets;
});
return aiSettings;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalizeAISettings method mutates the input array by deleting properties (id, table_widgets) from each setting object. This direct mutation could lead to unexpected behavior if the caller expects the original data to remain unchanged. Consider creating new objects instead of mutating the input, or clearly document this side effect.

Suggested change
aiSettings.forEach((setting) => {
delete setting.id;
setting.connection_id = connection;
delete setting.table_widgets;
});
return aiSettings;
return aiSettings.map((setting) => {
const { id: _ignoredId, table_widgets: _ignoredTableWidgets, ...rest } = setting;
return {
...rest,
connection_id: connection,
} as TableSettingsEntity;
});

Copilot uses AI. Check for mistakes.
} finally {
if (isConnectionTestedSuccessfully && !isConnectionTypeAgent(connectionCopy.type)) {
await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);
// await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented-out code should be removed rather than left in place. If the old scanDatabaseAndCreateWidgets method is no longer needed, it should be deleted. If it's being kept as a fallback or for backwards compatibility, the comment should explain why both methods are present and under what conditions each should be used.

Suggested change
// await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);

Copilot uses AI. Check for mistakes.
Comment on lines 115 to 118
if (isConnectionTestedSuccessfully && !isConnectionTypeAgent(connectionCopy.type)) {
await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);
// await this.sharedJobsService.scanDatabaseAndCreateWidgets(connectionCopy);
await this.sharedJobsService.scanDatabaseAndCreateSettingsAndWidgetsWithAI(connectionCopy);
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI-powered scanning is triggered in a finally block without any error propagation to the user. If the AI service fails (e.g., due to invalid credentials, model unavailability, or API quota limits), the connection will still be created successfully, but the user won't receive table settings or widgets. Since this is now the primary method for widget creation (the old scanDatabaseAndCreateWidgets is commented out), consider either: 1) Making the AI call part of the main flow with proper error handling and user notification, or 2) Implementing a fallback to the old method if AI generation fails.

Copilot uses AI. Check for mistakes.
});
try {
const response = await this.bedrockRuntimeClient.send(command);
const responseText = response.output.message?.content[0].text;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code accesses response.output.message?.content[0].text without checking if the content array has any elements. If the response has an empty content array, this will throw a runtime error. Add a check to ensure content array length is greater than 0 before accessing content[0].

Suggested change
const responseText = response.output.message?.content[0].text;
const content = response.output.message?.content;
const responseText = content && content.length > 0 ? content[0]?.text : undefined;

Copilot uses AI. Check for mistakes.
try {
return JSON.parse(cleanedResponse) as AIResponse;
} catch (error) {
throw new Error(`Failed to parse AI response: ${error.message}`);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error parsing logic in this method does not account for potential issues when accessing error.message. If the error caught is not an Error object (e.g., a string or other type is thrown), accessing error.message will result in undefined. Consider using a type guard or checking the error type before accessing its message property.

Suggested change
throw new Error(`Failed to parse AI response: ${error.message}`);
const errorMessage =
error instanceof Error
? error.message
: typeof error === 'string'
? error
: (() => {
try {
return JSON.stringify(error);
} catch {
return 'Unknown error';
}
})();
throw new Error(`Failed to parse AI response: ${errorMessage}`);

Copilot uses AI. Check for mistakes.
settings.table_name = tableSettings.table_name;
settings.display_name = tableSettings.display_name;
settings.list_fields = this.filterValidColumns(tableSettings.list_fields, validColumnNames);
settings.ordering_field = tableSettings.ordering_field;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering_field value from AI is not validated against validColumnNames before being assigned. While list_fields, search_fields, readonly_fields, and columns_view are all filtered through filterValidColumns, ordering_field is directly assigned without validation. If the AI suggests an invalid column name for ordering_field, this could cause runtime errors when the ordering is applied. Consider validating that ordering_field exists in validColumnNames, or setting it to null if invalid.

Suggested change
settings.ordering_field = tableSettings.ordering_field;
settings.ordering_field = validColumnNames.includes(tableSettings.ordering_field)
? tableSettings.ordering_field
: null;

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +95
@ApiOperation({ summary: 'Request AI settings and widgets creation for connection' })
@ApiResponse({
status: 200,
description: 'AI settings and widgets creation job has been queued.',
})
@Get('/ai/v2/setup/:connectionId')
public async requestAISettingsAndWidgetsCreation(
@SlugUuid('connectionId') connectionId: string,
@MasterPassword() masterPassword: string,
@UserId() userId: string,
): Promise<void> {
const connectionData = {
connectionId,
masterPwd: masterPassword,
cognitoUserName: userId,
};
return await this.requestAISettingsAndWidgetsCreationUseCase.execute(connectionData, InTransactionEnum.OFF);
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API documentation states 'AI settings and widgets creation job has been queued', but the implementation actually executes the job synchronously in the use case (await this.sharedJobsService.scanDatabaseAndCreateSettingsAndWidgetsWithAI). This is misleading - the endpoint will block until the entire AI generation and database operations complete. Either update the documentation to reflect the synchronous nature, or consider implementing actual asynchronous job queueing for better user experience and scalability.

Copilot uses AI. Check for mistakes.
@Artuomka Artuomka merged commit d9a5f93 into main Dec 22, 2025
25 checks passed
@Artuomka Artuomka deleted the backend_aws_bedrock branch December 22, 2025 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants