Skip to content

Commit

Permalink
Create assistant chat usecase and repositories (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
banghuazhao authored Jan 23, 2025
1 parent 58a11e1 commit 92dfb0f
Show file tree
Hide file tree
Showing 16 changed files with 451 additions and 6 deletions.
4 changes: 2 additions & 2 deletions android/version.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
versionName=6.2.3
versionCode=2024091519
versionName=6.2.6
versionCode=2024122601
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ class CompositeExpertRepositoryImpl implements CompositeExpertRepository {
}
return;
}



}


88 changes: 88 additions & 0 deletions packages/data/lib/repositories/assistant_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import 'dart:convert';
import 'dart:io';

import 'package:domain/entities/assistant.dart';
import 'package:domain/entities/thread.dart';
import 'package:http/http.dart' as http;

import 'package:domain/repositories_abstract/assistant_repository.dart';

import '../utils/api_constants.dart';

class AssistantRepositoryImp implements AssistantRepository {
final http.Client client;

AssistantRepositoryImp(
{required this.client});

@override
Future<Assistant> createCompositeAssistant() async {
final request =
http.Request('POST', Uri.parse(ApiConstants.assistantsEndpoint))
..headers.addAll({
'Content-Type': 'application/json',
'Authorization': 'Bearer ${ApiConstants.apiKey}',
'OpenAI-Beta': 'assistants=v2',
})
..body = jsonEncode({
"model": "gpt-4o",
"name": "Composites AI",
'description':
"You are an expert in composite materials and structures. Please answer questions related to composites simulation, design and manufacturing."
});

// 2. Send the request
final http.StreamedResponse streamedResponse = await client.send(request);
final String responseBody = await streamedResponse.stream.bytesToString();

// 3. Handle success or throw an error
if (streamedResponse.statusCode == 200 ||
streamedResponse.statusCode == 201) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
return Assistant.fromJson(jsonResponse);
} else {
// Provide as much detail as possible for debugging
throw HttpException(
'Failed to create assistant. '
'Status: ${streamedResponse.statusCode}, '
'Response: $responseBody',
uri: request.url,
);
}
}

@override
String getCompositeAssistantId() {
return "asst_pxUDI3A9Q8afCqT9cqgUkWQP";
}

@override
Future<Thread> createThread() async {
final request =
http.Request('POST', Uri.parse(ApiConstants.threadsEndpoint))
..headers.addAll({
'Content-Type': 'application/json',
'Authorization': 'Bearer ${ApiConstants.apiKey}',
'OpenAI-Beta': 'assistants=v2',
});

// 2. Send the request
final http.StreamedResponse streamedResponse = await client.send(request);
final String responseBody = await streamedResponse.stream.bytesToString();

// 3. Handle success or throw an error
if (streamedResponse.statusCode == 200 ||
streamedResponse.statusCode == 201) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
return Thread.fromJson(jsonResponse);
} else {
// Provide as much detail as possible for debugging
throw HttpException(
'Failed to create a thread. '
'Status: ${streamedResponse.statusCode}, '
'Response: $responseBody',
uri: request.url,
);
}
}
}
50 changes: 50 additions & 0 deletions packages/data/lib/repositories/messages_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'dart:convert';
import 'dart:io';

import 'package:domain/entities/assistant_message.dart';
import 'package:domain/entities/message.dart';
import 'package:domain/entities/thread.dart';
import 'package:domain/repositories_abstract/messages_repository.dart';
import 'package:http/http.dart' as http;

import '../utils/api_constants.dart';

class MessagesRepositoryImp implements MessagesRepository {
final http.Client client;

MessagesRepositoryImp({required this.client});

@override
Future<AssistantMessage> createMessage(Thread thread, Message message) async {
final request = http.Request('POST',
Uri.parse(ApiConstants.threadsEndpoint + "/${thread.id}/messages"))
..headers.addAll({
'Content-Type': 'application/json',
'Authorization': 'Bearer ${ApiConstants.apiKey}',
'OpenAI-Beta': 'assistants=v2',
})
..body = jsonEncode({
"role": "user",
"content": message.content,
});

// 2. Send the request
final http.StreamedResponse streamedResponse = await client.send(request);
final String responseBody = await streamedResponse.stream.bytesToString();

// 3. Handle success or throw an error
if (streamedResponse.statusCode == 200 ||
streamedResponse.statusCode == 201) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
return AssistantMessage.fromJson(jsonResponse);
} else {
// Provide as much detail as possible for debugging
throw HttpException(
'Failed to create a message. '
'Status: ${streamedResponse.statusCode}, '
'Response: $responseBody',
uri: request.url,
);
}
}
}
45 changes: 45 additions & 0 deletions packages/data/lib/repositories/threads_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'dart:convert';
import 'dart:io';

import 'package:domain/entities/thread.dart';
import 'package:domain/repositories_abstract/threads_repository.dart';
import 'package:http/http.dart' as http;

import '../utils/api_constants.dart';

class ThreadsRepositoryImp implements ThreadsRepository {
final http.Client client;

ThreadsRepositoryImp(
{required this.client});

@override
Future<Thread> createThread() async {
final request =
http.Request('POST', Uri.parse(ApiConstants.threadsEndpoint))
..headers.addAll({
'Content-Type': 'application/json',
'Authorization': 'Bearer ${ApiConstants.apiKey}',
'OpenAI-Beta': 'assistants=v2',
});

// 2. Send the request
final http.StreamedResponse streamedResponse = await client.send(request);
final String responseBody = await streamedResponse.stream.bytesToString();

// 3. Handle success or throw an error
if (streamedResponse.statusCode == 200 ||
streamedResponse.statusCode == 201) {
final Map<String, dynamic> jsonResponse = jsonDecode(responseBody);
return Thread.fromJson(jsonResponse);
} else {
// Provide as much detail as possible for debugging
throw HttpException(
'Failed to create a thread. '
'Status: ${streamedResponse.statusCode}, '
'Response: $responseBody',
uri: request.url,
);
}
}
}
2 changes: 2 additions & 0 deletions packages/data/lib/utils/api_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
class ApiConstants {
static const String baseUrl = 'https://api.openai.com/v1';
static const String chatCompletionsEndpoint = '$baseUrl/chat/completions';
static const String assistantsEndpoint = '$baseUrl/assistants';
static const String threadsEndpoint = '$baseUrl/threads';
static String apiKey = dotenv.env['OPENAI_API_KEY'] ?? "";
}
50 changes: 50 additions & 0 deletions packages/domain/lib/entities/assistant.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class Assistant {
final String id;
final String object;
final int createdAt;
final String name;
final String description;
final String model;
final dynamic instructions;
final List<dynamic> tools;
final double topP;
final double temperature;
final Map<String, dynamic> toolResources;
final Map<String, dynamic> metadata;
final String responseFormat;

Assistant({
required this.id,
required this.object,
required this.createdAt,
required this.name,
required this.description,
required this.model,
required this.instructions,
required this.tools,
required this.topP,
required this.temperature,
required this.toolResources,
required this.metadata,
required this.responseFormat,
});

/// Parses an [Assistant] from a JSON object
factory Assistant.fromJson(Map<String, dynamic> json) {
return Assistant(
id: json['id'] as String,
object: json['object'] as String,
createdAt: json['created_at'] as int,
name: json['name'] as String,
description: json['description'] as String,
model: json['model'] as String,
instructions: json['instructions'], // can be null
tools: (json['tools'] ?? []) as List<dynamic>,
topP: (json['top_p'] ?? 1.0).toDouble(),
temperature: (json['temperature'] ?? 1.0).toDouble(),
toolResources: (json['tool_resources'] ?? {}) as Map<String, dynamic>,
metadata: (json['metadata'] ?? {}) as Map<String, dynamic>,
responseFormat: json['response_format'] as String? ?? 'auto',
);
}
}
98 changes: 98 additions & 0 deletions packages/domain/lib/entities/assistant_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
class AssistantMessage {
final String id;
final String object;
final int createdAt;
final String? assistantId;
final String threadId;
final String? runId;
final String role;
final List<Content> content;
final List<dynamic> attachments;
final Map<String, dynamic> metadata;

AssistantMessage({
required this.id,
required this.object,
required this.createdAt,
this.assistantId,
required this.threadId,
this.runId,
required this.role,
required this.content,
required this.attachments,
required this.metadata,
});

factory AssistantMessage.fromJson(Map<String, dynamic> json) {
var contentList = <Content>[];
if (json['content'] != null) {
contentList = (json['content'] as List)
.map((i) => Content.fromJson(i))
.toList();
}

return AssistantMessage(
id: json['id'],
object: json['object'],
createdAt: json['created_at'],
assistantId: json['assistant_id'],
threadId: json['thread_id'],
runId: json['run_id'],
role: json['role'],
content: contentList,
attachments: json['attachments'] ?? [],
metadata: json['metadata'] ?? {},
);
}

Map<String, dynamic> toJson() => {
'id': id,
'object': object,
'created_at': createdAt,
'assistant_id': assistantId,
'thread_id': threadId,
'run_id': runId,
'role': role,
'content': content.map((e) => e.toJson()).toList(),
'attachments': attachments,
'metadata': metadata,
};
}

class Content {
final String type;
final TextContent text;

Content({required this.type, required this.text});

factory Content.fromJson(Map<String, dynamic> json) {
return Content(
type: json['type'],
text: TextContent.fromJson(json['text']),
);
}

Map<String, dynamic> toJson() => {
'type': type,
'text': text.toJson(),
};
}

class TextContent {
final String value;
final List<dynamic> annotations;

TextContent({required this.value, required this.annotations});

factory TextContent.fromJson(Map<String, dynamic> json) {
return TextContent(
value: json['value'],
annotations: json['annotations'] ?? [],
);
}

Map<String, dynamic> toJson() => {
'value': value,
'annotations': annotations,
};
}
33 changes: 33 additions & 0 deletions packages/domain/lib/entities/thread.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Thread {
final String id;
final String object;
final int createdAt;
final Map<String, dynamic> metadata;
final Map<String, dynamic> toolResources;

Thread({
required this.id,
required this.object,
required this.createdAt,
required this.metadata,
required this.toolResources,
});

factory Thread.fromJson(Map<String, dynamic> json) {
return Thread(
id: json['id'],
object: json['object'],
createdAt: json['created_at'],
metadata: json['metadata'] ?? {},
toolResources: json['tool_resources'] ?? {},
);
}

Map<String, dynamic> toJson() => {
'id': id,
'object': object,
'created_at': createdAt,
'metadata': metadata,
'tool_resources': toolResources,
};
}
Loading

0 comments on commit 92dfb0f

Please sign in to comment.