gemini_llm_service_impl.dart 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:firebase_ai/firebase_ai.dart';
  6. import 'package:gemini_live_app/services/llm_service.dart';
  7. /// A concrete implementation of the [LLMService] interface that uses the
  8. /// Gemini Live API via `firebase_ai`.
  9. class GeminiLLMServiceImpl implements LLMService {
  10. late LiveGenerativeModel _liveGenerativeModel;
  11. LiveSession? _liveSession;
  12. StreamSubscription? _audioStreamSubscription;
  13. StreamSubscription? _responseSubscription;
  14. @override
  15. final connectionStatus = ValueNotifier<bool>(false);
  16. final _incomingAudioStreamController =
  17. StreamController<Uint8List>.broadcast();
  18. @override
  19. Stream<Uint8List> get onAudioReceived =>
  20. _incomingAudioStreamController.stream;
  21. /// Connects to the Gemini Live API and initializes a live session.
  22. @override
  23. Future<void> connect() async {
  24. try {
  25. final String jsonContent = await rootBundle.loadString(
  26. 'assets/config.json',
  27. );
  28. final Map<String, dynamic> config = json.decode(jsonContent);
  29. final String modelName = config['model_name'];
  30. final String systemInstructionText = await rootBundle.loadString(
  31. 'assets/system_instructions.txt',
  32. );
  33. final ai = FirebaseAI.googleAI();
  34. _liveGenerativeModel = ai.liveGenerativeModel(
  35. model: modelName,
  36. liveGenerationConfig: LiveGenerationConfig(
  37. responseModalities: [ResponseModalities.audio],
  38. ),
  39. systemInstruction: Content('model', [TextPart(systemInstructionText)]),
  40. );
  41. try {
  42. _liveSession = await _liveGenerativeModel.connect();
  43. } catch (e) {
  44. connectionStatus.value = false;
  45. rethrow;
  46. }
  47. _listenForResponses();
  48. connectionStatus.value = true;
  49. } catch (e) {
  50. connectionStatus.value = false;
  51. rethrow;
  52. }
  53. }
  54. /// Sends the user's audio stream to the Gemini Live API.
  55. @override
  56. void sendAudioStream(Stream<Uint8List> audioStream) {
  57. if (_liveSession == null || !connectionStatus.value) {
  58. return;
  59. }
  60. _audioStreamSubscription?.cancel();
  61. _audioStreamSubscription = audioStream
  62. .map((data) {
  63. return InlineDataPart('audio/pcm', data);
  64. })
  65. .listen(
  66. (chunk) async {
  67. try {
  68. await _liveSession!.sendAudioRealtime(chunk);
  69. } catch (e) {
  70. connectionStatus.value = false;
  71. _audioStreamSubscription?.cancel();
  72. }
  73. },
  74. onDone: () {},
  75. onError: (e) {
  76. connectionStatus.value = false;
  77. _audioStreamSubscription?.cancel();
  78. },
  79. cancelOnError: true,
  80. );
  81. }
  82. /// Listens for incoming audio responses from the Gemini Live API.
  83. void _listenForResponses() {
  84. if (_liveSession == null) return;
  85. _responseSubscription?.cancel();
  86. _responseSubscription = _liveSession!.receive().listen(
  87. (message) {
  88. if (message.message is LiveServerContent &&
  89. (message.message as LiveServerContent).modelTurn?.parts != null) {
  90. final serverContent = message.message as LiveServerContent;
  91. for (final part in serverContent.modelTurn!.parts) {
  92. if (part is InlineDataPart) {
  93. final audioBytes = part.bytes;
  94. _incomingAudioStreamController.add(audioBytes);
  95. }
  96. }
  97. }
  98. },
  99. onDone: () {
  100. connectionStatus.value = false;
  101. },
  102. onError: (e) {
  103. connectionStatus.value = false;
  104. },
  105. cancelOnError: true,
  106. );
  107. }
  108. /// Disposes of the resources used by the service.
  109. @override
  110. void dispose() {
  111. _liveSession?.close();
  112. _audioStreamSubscription?.cancel();
  113. _responseSubscription?.cancel();
  114. connectionStatus.dispose();
  115. _incomingAudioStreamController.close();
  116. }
  117. }