Skip to content

TF- Jmap method for aibot #3799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions aibot/data/network /suggest_reply_api.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/jmap_request.dart';
import 'package:aibot/suggestReply/suggest_reply_response.dart';
import 'package:aibot/suggestReply/suggest_reply_method.dart';

class SuggestReplyApi {
final HttpClient _httpClient;
SuggestReplyApi(this._httpClient);

Future<SuggestReplyResponse?> suggestReply(
{required AccountId accountId,
required String userInput,
String? mailId}) async {
final processingInvocation = ProcessingInvocation();
final requestBuilder =
JmapRequestBuilder(_httpClient, processingInvocation);

final suggestReplyMethod = SuggestReplyMethod(
accountId: accountId,
emailId: mailId ?? '',
userInput: userInput,
);

final aiBotSuggestInvocation =
requestBuilder.invocation(suggestReplyMethod);

final response = await (requestBuilder
..usings(suggestReplyMethod.requiredCapabilities))
.build()
.execute();

final result = response.parse<SuggestReplyResponse>(
aiBotSuggestInvocation.methodCallId, SuggestReplyResponse.deserialize);

return result;
}
}
44 changes: 44 additions & 0 deletions aibot/lib/suggestReply/suggest_reply_method.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:jmap_dart_client/http/converter/account_id_converter.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/method/method.dart';
import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'suggest_reply_method.g.dart';

@AccountIdConverter()
@JsonSerializable(explicitToJson: true)
class SuggestReplyMethod extends MethodRequiringAccountId {
static String aibotCapability = 'com.linagora.tmail:params:jmap:aibot';

static final capabilityIdentifier =
CapabilityIdentifier(Uri.parse(aibotCapability));

final String emailId;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
final String emailId;
final EmailId emailId;

final String userInput;

SuggestReplyMethod({
required AccountId accountId,
required this.emailId,
required this.userInput,
}) : super(accountId);

@override
MethodName get methodName => MethodName('AIBot/suggestReply');

@override
Set<CapabilityIdentifier> get requiredCapabilities => {
capabilityIdentifier,
CapabilityIdentifier.jmapCore,
};

@override
List<Object?> get props => [accountId, emailId, userInput];

@override
Map<String, dynamic> toJson() => _$SuggestReplyMethodToJson(this);

factory SuggestReplyMethod.fromJson(Map<String, dynamic> json) =>
_$SuggestReplyMethodFromJson(json);
}
26 changes: 26 additions & 0 deletions aibot/lib/suggestReply/suggest_reply_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:jmap_dart_client/http/converter/account_id_converter.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/method/method_response.dart';
import 'package:json_annotation/json_annotation.dart';

part 'suggest_reply_response.g.dart';

@AccountIdConverter()
@JsonSerializable()
class SuggestReplyResponse extends ResponseRequiringAccountId {
final String suggestion;

SuggestReplyResponse(AccountId accountId, this.suggestion) : super(accountId);

factory SuggestReplyResponse.fromJson(Map<String, dynamic> json) =>
_$SuggestReplyResponseFromJson(json);

static SuggestReplyResponse deserialize(Map<String, dynamic> json) {
return SuggestReplyResponse.fromJson(json);
}

Map<String, dynamic> toJson() => _$SuggestReplyResponseToJson(this);

@override
List<Object?> get props => [accountId, suggestion];
}
88 changes: 88 additions & 0 deletions aibot/test/aibot/suggestReply/suggest_reply_method_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/jmap_request.dart';
import 'package:aibot/suggestReply/suggest_reply_method.dart';
import 'package:aibot/suggestReply/suggest_reply_response.dart';

void main() {
group('test suggest reply method', () {
test('suggest reply method and response parsing', () async {
final baseOption = BaseOptions(method: 'POST');
final dio = Dio(baseOption)..options.baseUrl = 'http://domain.com/jmap';
final dioAdapter = DioAdapter(dio: dio);

final accountId = AccountId(Id(
'29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6'));
const emailId = 'm123456';
const userInput = 'Can you help me reply to this?';
const expectedSuggestion =
'Of course, here is a suggestion for your reply.';

dioAdapter.onPost(
'',
(server) => server.reply(
200,
{
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"AIBot/suggestReply",
{
"accountId": accountId.id.value,
"suggestion": expectedSuggestion,
},
"c0"
]
]
},
),
data: {
"using": [
SuggestReplyMethod.capabilityIdentifier.value.toString(),
"urn:ietf:params:jmap:core"
],
"methodCalls": [
[
"AIBot/suggestReply",
{
"accountId": accountId.id.value,
"emailId": emailId,
"userInput": userInput
},
"c0"
]
]
},
);

final httpClient = HttpClient(dio);
final processingInvocation = ProcessingInvocation();
final requestBuilder =
JmapRequestBuilder(httpClient, processingInvocation);

final suggestReplyMethod = SuggestReplyMethod(
accountId: accountId,
emailId: emailId,
userInput: userInput,
);

final invocation = requestBuilder.invocation(suggestReplyMethod);
final response = await (requestBuilder
..usings(suggestReplyMethod.requiredCapabilities))
.build()
.execute();
final result = response.parse<SuggestReplyResponse>(
invocation.methodCallId,
SuggestReplyResponse.deserialize,
);

expect(result, isNotNull);
expect(result?.suggestion, equals(expectedSuggestion));
expect(result?.accountId, equals(accountId));
});
});
}
30 changes: 30 additions & 0 deletions lib/features/aibot/domain/ai_bot_suggest_reply.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:tmail_ui_user/features/aibot/domain/repository/aibot_repository.dart';
import 'package:tmail_ui_user/features/aibot/domain/state/suggest_reply_state.dart';

class AiBotSuggestReply {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
class AiBotSuggestReply {
class AiBotSuggestReplyInteractor {

final AibotRepository _repository;

AiBotSuggestReply(this._repository);

Stream<dartz.Either<Failure, Success>> execute({
required AccountId accountId,
required String userInput,
String? mailId,
}) async* {
yield dartz.Right(SuggestReplyLoading());

final suggestion = await _repository.suggestReply(
accountId: accountId,
userInput: userInput,
emailId: mailId ?? '',
);

yield suggestion.fold(
(failure) => dartz.Left(SuggestReplyFailure(failure.toString())),
(suggestion) => dartz.Right(SuggestReplySuccess(suggestion)),
);
}
}
11 changes: 11 additions & 0 deletions lib/features/aibot/domain/repository/aibot_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';

abstract class AibotRepository {
Future<Either<Failure, String>> suggestReply({
required AccountId accountId,
required String emailId,
required String userInput,
});
}
20 changes: 20 additions & 0 deletions lib/features/aibot/domain/state/suggest_reply_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';

class SuggestReplyLoading extends UIState {
@override
List<Object?> get props => [];
}

class SuggestReplySuccess extends UIState {
final String suggestion;

SuggestReplySuccess(this.suggestion);

@override
List<Object?> get props => [suggestion];
}

class SuggestReplyFailure extends FeatureFailure {
SuggestReplyFailure(dynamic exception) : super(exception: exception);
}
Loading