From e9f04dd661eea14d8eb83bfc2ab26325d788f113 Mon Sep 17 00:00:00 2001 From: Dillon Nys Date: Tue, 8 Oct 2024 20:37:53 -0700 Subject: [PATCH] chore: Update `gemini` example - Migrates the Gemini example to v1 - Updates the prompt generation method to be streaming --- examples/gemini/README.md | 4 +- examples/gemini/analysis_options.yaml | 4 + .../lib/gemini_example_client.dart} | 34 +- .../celest/client/lib/src/functions.dart | 204 ++++++ .../celest/client/lib/src/serializers.dart | 651 ++++++++++++++++++ examples/gemini/celest/client/pubspec.yaml | 26 + examples/gemini/celest/functions | 1 + .../gemini/celest/generated/resources.dart | 14 - .../celest/lib/fix_data/v1_migration.yaml | 13 + .../celest/lib/src/client/functions.dart | 107 --- .../celest/lib/src/client/serializers.dart | 85 --- .../{ => lib/src}/functions/gemini.dart | 28 +- .../celest/{ => lib/src}/generated/README.md | 0 .../lib/src/generated/cloud.celest.dart | 42 ++ .../lib/src/generated/config.celest.dart | 56 ++ examples/gemini/celest/lib/src/project.dart | 7 + examples/gemini/celest/project.dart | 6 +- examples/gemini/celest/pubspec.yaml | 12 +- examples/gemini/ios/Podfile | 3 +- examples/gemini/ios/Podfile.lock | 28 + .../ios/Runner.xcodeproj/project.pbxproj | 112 +++ .../contents.xcworkspacedata | 3 + examples/gemini/lib/main.dart | 184 ++--- examples/gemini/macos/Podfile | 2 +- examples/gemini/macos/Podfile.lock | 28 + .../macos/Runner.xcodeproj/project.pbxproj | 98 ++- .../contents.xcworkspacedata | 3 + .../gemini/macos/Runner/AppDelegate.swift | 2 +- examples/gemini/pubspec.yaml | 7 +- examples/gemini/web/index.html | 29 +- 30 files changed, 1436 insertions(+), 357 deletions(-) rename examples/gemini/celest/{lib/client.dart => client/lib/gemini_example_client.dart} (56%) create mode 100644 examples/gemini/celest/client/lib/src/functions.dart create mode 100644 examples/gemini/celest/client/lib/src/serializers.dart create mode 100644 examples/gemini/celest/client/pubspec.yaml create mode 120000 examples/gemini/celest/functions delete mode 100644 examples/gemini/celest/generated/resources.dart create mode 100644 examples/gemini/celest/lib/fix_data/v1_migration.yaml delete mode 100644 examples/gemini/celest/lib/src/client/functions.dart delete mode 100644 examples/gemini/celest/lib/src/client/serializers.dart rename examples/gemini/celest/{ => lib/src}/functions/gemini.dart (81%) rename examples/gemini/celest/{ => lib/src}/generated/README.md (100%) create mode 100644 examples/gemini/celest/lib/src/generated/cloud.celest.dart create mode 100644 examples/gemini/celest/lib/src/generated/config.celest.dart create mode 100644 examples/gemini/celest/lib/src/project.dart mode change 100644 => 120000 examples/gemini/celest/project.dart create mode 100644 examples/gemini/ios/Podfile.lock create mode 100644 examples/gemini/macos/Podfile.lock diff --git a/examples/gemini/README.md b/examples/gemini/README.md index 499423a5..145d5a04 100644 --- a/examples/gemini/README.md +++ b/examples/gemini/README.md @@ -18,9 +18,9 @@ $ flutter pub get Download Celest from the [Celest download page](https://celest.dev/download), and run the installer to get the Celest CLI. -Open this folder (`examples/gemini`) in your favorite IDE, navigate to the `celest/config` folder, and create a `.env` file. This will contain your Gemini API key. To learn more about obtaining an Gemini API key, follow this [guide](https://ai.google.dev/tutorials/setup). +Open this folder (`examples/gemini`) in your favorite IDE, navigate to the `celest` folder, and create a `.env` file. This will contain your Gemini API key. To learn more about obtaining an Gemini API key, follow this [guide](https://ai.google.dev/tutorials/setup). -The `.env` file you created in the `config` folder should contain the following key-value pair: +The `.env` file you created should contain the following key-value pair: ```shell GEMINI_API_KEY= diff --git a/examples/gemini/analysis_options.yaml b/examples/gemini/analysis_options.yaml index 0d290213..ac83d24f 100644 --- a/examples/gemini/analysis_options.yaml +++ b/examples/gemini/analysis_options.yaml @@ -26,3 +26,7 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options + +analyzer: + plugins: + - celest diff --git a/examples/gemini/celest/lib/client.dart b/examples/gemini/celest/client/lib/gemini_example_client.dart similarity index 56% rename from examples/gemini/celest/lib/client.dart rename to examples/gemini/celest/client/lib/gemini_example_client.dart index e33f0900..4b30309c 100644 --- a/examples/gemini/celest/lib/client.dart +++ b/examples/gemini/celest/client/lib/gemini_example_client.dart @@ -1,17 +1,18 @@ // Generated by Celest. This file should not be modified manually, but // it can be checked into version control. -// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import, deprecated_member_use library; // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:io' as _$io; -import 'package:celest_core/_internal.dart'; -import 'package:celest_core/src/util/globals.dart'; +import 'package:celest_core/_internal.dart' as _$celest; +import 'package:celest_core/celest_core.dart' as _$celest; +import 'package:celest_core/src/util/globals.dart' as _$celest; +import 'package:gemini_example_client/src/functions.dart'; +import 'package:gemini_example_client/src/serializers.dart'; import 'package:http/http.dart' as _$http; - -import 'src/client/functions.dart'; -import 'src/client/serializers.dart'; +import 'package:native_storage/native_storage.dart' as _$native_storage; final Celest celest = Celest(); @@ -19,23 +20,23 @@ enum CelestEnvironment { local; Uri get baseUri => switch (this) { - local => kIsWeb || !_$io.Platform.isAndroid - ? Uri.parse('http://localhost:7777') - : Uri.parse('http://10.0.2.2:7777'), + local => _$celest.kIsWeb || !_$io.Platform.isAndroid + ? Uri.parse('http://localhost:7786') + : Uri.parse('http://10.0.2.2:7786'), }; } -class Celest with CelestBase { +class Celest with _$celest.CelestBase { var _initialized = false; late CelestEnvironment _currentEnvironment; - @override - late final NativeStorage nativeStorage = NativeStorage(scope: 'celest'); + late final _$native_storage.NativeStorage nativeStorage = + _$native_storage.NativeStorage(scope: 'celest'); @override late _$http.Client httpClient = - CelestHttpClient(secureStorage: nativeStorage.secure); + _$celest.CelestHttpClient(secureStorage: nativeStorage.secure); late Uri _baseUri; @@ -57,11 +58,14 @@ class Celest with CelestBase { CelestFunctions get functions => _checkInitialized(() => _functions); - void init({CelestEnvironment environment = CelestEnvironment.local}) { + void init({ + CelestEnvironment environment = CelestEnvironment.local, + _$celest.Serializers? serializers, + }) { _currentEnvironment = environment; _baseUri = environment.baseUri; if (!_initialized) { - initSerializers(); + initSerializers(serializers: serializers); } _initialized = true; } diff --git a/examples/gemini/celest/client/lib/src/functions.dart b/examples/gemini/celest/client/lib/src/functions.dart new file mode 100644 index 00000000..cc52b8f0 --- /dev/null +++ b/examples/gemini/celest/client/lib/src/functions.dart @@ -0,0 +1,204 @@ +// Generated by Celest. This file should not be modified manually, but +// it can be checked into version control. +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import, deprecated_member_use + +library; // ignore_for_file: no_leading_underscores_for_library_prefixes + +import 'dart:async' as _$async; +import 'dart:convert' as _$convert; + +import 'package:celest/celest.dart' as _$celest; +import 'package:celest_core/celest_core.dart' as _$celest; +import 'package:celest_core/src/exception/cloud_exception.dart' as _$celest; +import 'package:celest_core/src/exception/serialization_exception.dart' + as _$celest; +import 'package:gemini_example_client/gemini_example_client.dart'; +import 'package:google_generative_ai/src/error.dart' as _$error; +import 'package:http/src/exception.dart' as _$exception; + +class CelestFunctions { + final gemini = CelestFunctionsGemini(); +} + +class CelestFunctionsGemini { + Never _throwError({ + required int? $statusCode, + required Map $body, + }) { + final $error = ($body['@error'] as Map?); + $statusCode ??= ($error?['status'] as num?)?.toInt(); + final $code = ($error?['code'] as String); + final $message = ($body['message'] as String?); + final $details = ($body['@'] as _$celest.JsonValue?) ?? + ($body['details'] as _$celest.JsonMap?); + switch ($code) { + case r'dart.convert.JsonUnsupportedObjectError': + throw _$celest.Serializers.instance + .deserialize<_$convert.JsonUnsupportedObjectError>($details); + case r'dart.async.AsyncError': + throw _$celest.Serializers.instance + .deserialize<_$async.AsyncError>($details); + case r'dart.async.TimeoutException': + throw _$celest.Serializers.instance + .deserialize<_$async.TimeoutException>($details); + case r'dart.core.Error': + throw _$celest.Serializers.instance.deserialize($details); + case r'dart.core.AssertionError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.TypeError': + throw _$celest.Serializers.instance.deserialize($details); + case r'dart.core.ArgumentError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.RangeError': + throw _$celest.Serializers.instance.deserialize($details); + case r'dart.core.IndexError': + throw _$celest.Serializers.instance.deserialize($details); + case r'dart.core.UnsupportedError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.UnimplementedError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.StateError': + throw _$celest.Serializers.instance.deserialize($details); + case r'dart.core.ConcurrentModificationError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.OutOfMemoryError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.StackOverflowError': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.FormatException': + throw _$celest.Serializers.instance + .deserialize($details); + case r'dart.core.IntegerDivisionByZeroException': + throw _$celest.Serializers.instance + .deserialize($details); + case r'celest.core.v1.CloudException': + throw _$celest.Serializers.instance + .deserialize<_$celest.CloudException>($details); + case r'celest.core.v1.CancelledException': + throw _$celest.Serializers.instance + .deserialize<_$celest.CancelledException>($details); + case r'celest.core.v1.UnknownError': + throw _$celest.Serializers.instance + .deserialize<_$celest.UnknownError>($details); + case r'celest.core.v1.BadRequestException': + throw _$celest.Serializers.instance + .deserialize<_$celest.BadRequestException>($details); + case r'celest.core.v1.UnauthorizedException': + throw _$celest.Serializers.instance + .deserialize<_$celest.UnauthorizedException>($details); + case r'celest.core.v1.NotFoundException': + throw _$celest.Serializers.instance + .deserialize<_$celest.NotFoundException>($details); + case r'celest.core.v1.AlreadyExistsException': + throw _$celest.Serializers.instance + .deserialize<_$celest.AlreadyExistsException>($details); + case r'celest.core.v1.PermissionDeniedException': + throw _$celest.Serializers.instance + .deserialize<_$celest.PermissionDeniedException>($details); + case r'celest.core.v1.ResourceExhaustedException': + throw _$celest.Serializers.instance + .deserialize<_$celest.ResourceExhaustedException>($details); + case r'celest.core.v1.FailedPreconditionException': + throw _$celest.Serializers.instance + .deserialize<_$celest.FailedPreconditionException>($details); + case r'celest.core.v1.AbortedException': + throw _$celest.Serializers.instance + .deserialize<_$celest.AbortedException>($details); + case r'celest.core.v1.OutOfRangeException': + throw _$celest.Serializers.instance + .deserialize<_$celest.OutOfRangeException>($details); + case r'celest.core.v1.UnimplementedError': + throw _$celest.Serializers.instance + .deserialize<_$celest.UnimplementedError>($details); + case r'celest.core.v1.InternalServerError': + throw _$celest.Serializers.instance + .deserialize<_$celest.InternalServerError>($details); + case r'celest.core.v1.UnavailableError': + throw _$celest.Serializers.instance + .deserialize<_$celest.UnavailableError>($details); + case r'celest.core.v1.DataLossError': + throw _$celest.Serializers.instance + .deserialize<_$celest.DataLossError>($details); + case r'celest.core.v1.DeadlineExceededError': + throw _$celest.Serializers.instance + .deserialize<_$celest.DeadlineExceededError>($details); + case r'celest.core.v1.SerializationException': + throw _$celest.Serializers.instance + .deserialize<_$celest.SerializationException>($details); + case r'google_generative_ai.GenerativeAIException': + throw _$celest.Serializers.instance + .deserialize<_$error.GenerativeAIException>($details); + case r'google_generative_ai.InvalidApiKey': + throw _$celest.Serializers.instance + .deserialize<_$error.InvalidApiKey>($details); + case r'google_generative_ai.UnsupportedUserLocation': + throw _$celest.Serializers.instance + .deserialize<_$error.UnsupportedUserLocation>($details); + case r'google_generative_ai.ServerException': + throw _$celest.Serializers.instance + .deserialize<_$error.ServerException>($details); + case r'http.ClientException': + throw _$celest.Serializers.instance + .deserialize<_$exception.ClientException>($details); + default: + throw _$celest.CloudException.http( + status: $statusCode, + code: $code, + message: $message, + details: $details, + ); + } + } + + /// Returns a list of available models. + @_$celest.CloudFunction( + api: 'gemini', + function: 'availableModels', + ) + Future> availableModels() async { + final $response = await celest.httpClient.post( + celest.baseUri.resolve('/gemini/available-models'), + headers: {'Content-Type': 'application/json; charset=utf-8'}, + ); + final $body = _$convert.jsonDecode($response.body); + if ($response.statusCode != 200) { + _throwError( + $statusCode: $response.statusCode, + $body: $body, + ); + } + return ($body as Iterable).map((el) => (el as String)).toList(); + } + + /// Prompts the Gemini [modelName] with the given [prompt] and [parameters]. + /// + /// Returns the generated text. + @_$celest.CloudFunction( + api: 'gemini', + function: 'generateContent', + ) + Stream generateContent({ + required String modelName, + required String prompt, + }) { + final $channel = celest.eventClient + .connect(celest.baseUri.resolve('/gemini/generate-content')); + $channel.sink.add({ + r'modelName': modelName, + r'prompt': prompt, + }); + return $channel.stream.map(($event) { + if ($event is Map && $event.containsKey('@error')) { + _throwError($statusCode: -1, $body: $event); + } + return ($event as String); + }); + } +} diff --git a/examples/gemini/celest/client/lib/src/serializers.dart b/examples/gemini/celest/client/lib/src/serializers.dart new file mode 100644 index 00000000..aaeecd71 --- /dev/null +++ b/examples/gemini/celest/client/lib/src/serializers.dart @@ -0,0 +1,651 @@ +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import, deprecated_member_use + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _$async; +import 'dart:convert' as _$convert; + +import 'package:celest_core/celest_core.dart' as _$celest; +import 'package:celest_core/src/exception/cloud_exception.dart' as _$celest; +import 'package:celest_core/src/exception/serialization_exception.dart' + as _$celest; +import 'package:celest_core/src/serialization/json_value.dart' as _$celest; +import 'package:google_generative_ai/src/error.dart' as _$error; +import 'package:http/src/exception.dart' as _$exception; + +void initSerializers({_$celest.Serializers? serializers}) { + return _$async.runZoned( + () { + _$celest.Serializers.instance.put( + _$celest.Serializer.define<_$async.AsyncError, Map>( + serialize: ($value) => { + r'error': $value.error, + r'stackTrace': _$celest.Serializers.instance + .serialize($value.stackTrace), + }, + deserialize: ($serialized) { + return _$async.AsyncError( + $serialized[r'error']!, + _$celest.Serializers.instance + .deserialize($serialized[r'stackTrace']), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$async.TimeoutException, Map>( + serialize: ($value) => { + if ($value.message case final message?) r'message': message, + if (_$celest.Serializers.instance + .serialize($value.duration) + case final duration?) + r'duration': duration, + }, + deserialize: ($serialized) { + return _$async.TimeoutException( + ($serialized[r'message'] as String?), + _$celest.Serializers.instance + .deserialize($serialized[r'duration']), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$convert.JsonUnsupportedObjectError, Map>( + serialize: ($value) => { + if ($value.unsupportedObject case final unsupportedObject?) + r'unsupportedObject': unsupportedObject, + if ($value.cause case final cause?) r'cause': cause, + if ($value.partialResult case final partialResult?) + r'partialResult': partialResult, + }, + deserialize: ($serialized) { + return _$convert.JsonUnsupportedObjectError( + $serialized[r'unsupportedObject'], + cause: $serialized[r'cause'], + partialResult: ($serialized[r'partialResult'] as String?), + ); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define?>( + serialize: ($value) => { + r'invalidValue': $value.invalidValue, + if ($value.name case final name?) r'name': name, + r'message': $value.message, + }, + deserialize: ($serialized) { + return ArgumentError( + $serialized?[r'message'], + ($serialized?[r'name'] as String?), + ); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define?>( + serialize: ($value) => { + if ($value.message case final message?) r'message': message + }, + deserialize: ($serialized) { + return AssertionError($serialized?[r'message']); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + ConcurrentModificationError, Map?>( + serialize: ($value) => { + if ($value.modifiedObject case final modifiedObject?) + r'modifiedObject': modifiedObject + }, + deserialize: ($serialized) { + return ConcurrentModificationError($serialized?[r'modifiedObject']); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define?>( + serialize: ($value) => { + if (_$celest.Serializers.instance + .serialize($value.stackTrace) + case final stackTrace?) + r'stackTrace': stackTrace + }, + deserialize: ($serialized) { + return Error(); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define?>( + serialize: ($value) => { + r'message': $value.message, + r'source': $value.source, + if ($value.offset case final offset?) r'offset': offset, + }, + deserialize: ($serialized) { + return FormatException( + (($serialized?[r'message'] as String?)) ?? '', + $serialized?[r'source'], + ($serialized?[r'offset'] as num?)?.toInt(), + ); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define>( + serialize: ($value) => { + if ($value.name case final name?) r'name': name, + r'message': $value.message, + if ($value.indexable case final indexable?) r'indexable': indexable, + r'length': $value.length, + r'invalidValue': $value.invalidValue, + r'start': $value.start, + r'end': $value.end, + }, + deserialize: ($serialized) { + return IndexError( + ($serialized[r'invalidValue'] as num).toInt(), + $serialized[r'indexable'], + ($serialized[r'name'] as String?), + ($serialized[r'message'] as String?), + ($serialized[r'length'] as num?)?.toInt(), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + IntegerDivisionByZeroException, Map?>( + serialize: ($value) => { + if ($value.message case final message?) r'message': message + }, + deserialize: ($serialized) { + return IntegerDivisionByZeroException(); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define?>( + serialize: ($value) => const {}, + deserialize: ($serialized) { + return OutOfMemoryError(); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define>( + serialize: ($value) => { + if ($value.name case final name?) r'name': name, + r'message': $value.message, + if ($value.start case final start?) r'start': start, + if ($value.end case final end?) r'end': end, + if ($value.invalidValue case final invalidValue?) + r'invalidValue': invalidValue, + }, + deserialize: ($serialized) { + return RangeError($serialized[r'message']); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define?>( + serialize: ($value) => const {}, + deserialize: ($serialized) { + return StackOverflowError(); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return StateError(($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance + .put(_$celest.Serializer.define?>( + serialize: ($value) => const {}, + deserialize: ($serialized) { + return TypeError(); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define?>( + serialize: ($value) => { + if ($value.message case final message?) r'message': message + }, + deserialize: ($serialized) { + return UnimplementedError(($serialized?[r'message'] as String?)); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define>( + serialize: ($value) => { + if ($value.message case final message?) r'message': message + }, + deserialize: ($serialized) { + return UnsupportedError(($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.AbortedException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.AbortedException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.AlreadyExistsException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.AlreadyExistsException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.BadRequestException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.BadRequestException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.CancelledException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.CancelledException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.CloudException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.CloudException.fromJson($serialized); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.DataLossError, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.DataLossError( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.DeadlineExceededError, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.DeadlineExceededError( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.FailedPreconditionException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.FailedPreconditionException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.InternalServerError, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.InternalServerError( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.NotFoundException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.NotFoundException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.OutOfRangeException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.OutOfRangeException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.PermissionDeniedException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.PermissionDeniedException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.ResourceExhaustedException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.ResourceExhaustedException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.UnauthorizedException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.UnauthorizedException( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.UnavailableError, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.UnavailableError( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.UnimplementedError, Map?>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.UnimplementedError( + ($serialized?[r'message'] as String?)); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.UnknownError, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.UnknownError( + ($serialized[r'message'] as String?), + details: + _$celest.Serializers.instance.deserialize<_$celest.JsonValue?>( + $serialized[r'details'], + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ), + ); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$celest.SerializationException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize<_$celest.JsonValue?>( + $value.details, + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ) + case final details?) + r'details': details, + }, + deserialize: ($serialized) { + return _$celest.SerializationException( + ($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance.put( + _$celest.Serializer.define<_$celest.JsonValue, Object>( + serialize: ($value) => $value.value, + deserialize: ($serialized) { + return _$celest.JsonValue($serialized); + }, + ), + const _$celest.TypeToken<_$celest.JsonValue?>('JsonValue'), + ); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$error.GenerativeAIException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.GenerativeAIException( + ($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$error.InvalidApiKey, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.InvalidApiKey(($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$error.ServerException, Map>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.ServerException(($serialized[r'message'] as String)); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$error.UnsupportedUserLocation, Map?>( + serialize: ($value) => {r'message': $value.message}, + deserialize: ($serialized) { + return _$error.UnsupportedUserLocation(); + }, + )); + _$celest.Serializers.instance.put(_$celest.Serializer.define< + _$exception.ClientException, Map>( + serialize: ($value) => { + r'message': $value.message, + if (_$celest.Serializers.instance.serialize($value.uri) + case final uri?) + r'uri': uri, + }, + deserialize: ($serialized) { + return _$exception.ClientException( + ($serialized[r'message'] as String), + _$celest.Serializers.instance + .deserialize($serialized[r'uri']), + ); + }, + )); + }, + zoneValues: {_$celest.Serializers: serializers}, + ); +} diff --git a/examples/gemini/celest/client/pubspec.yaml b/examples/gemini/celest/client/pubspec.yaml new file mode 100644 index 00000000..aaf2a285 --- /dev/null +++ b/examples/gemini/celest/client/pubspec.yaml @@ -0,0 +1,26 @@ +name: gemini_example_client +description: The Celest client for gemini_example. +publish_to: none + +environment: + sdk: ^3.4.0 + +dependencies: + celest_backend: + path: .. + celest: '^1.0.0-0' + celest_core: '^1.0.0-0' + meta: '^1.12.0' + native_storage: '^0.2.2' + +dev_dependencies: {} + +dependency_overrides: + celest: + path: ../../../../packages/celest + celest_core: + path: ../../../../packages/celest_core + celest_auth: + path: ../../../../packages/celest_auth + celest_cloud: + path: ../../../../packages/celest_cloud diff --git a/examples/gemini/celest/functions b/examples/gemini/celest/functions new file mode 120000 index 00000000..6e1f1720 --- /dev/null +++ b/examples/gemini/celest/functions @@ -0,0 +1 @@ +/Users/dillonnys/celest/cloud/celest/examples/gemini/celest/lib/src/functions \ No newline at end of file diff --git a/examples/gemini/celest/generated/resources.dart b/examples/gemini/celest/generated/resources.dart deleted file mode 100644 index 218da18a..00000000 --- a/examples/gemini/celest/generated/resources.dart +++ /dev/null @@ -1,14 +0,0 @@ -// Generated by Celest. This file should not be modified manually, but -// it can be checked into version control. -// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import - -library; - -import 'package:celest/celest.dart'; - -@Deprecated('Use `env` instead.') -typedef Env = env; - -abstract final class env { - static const geminiApiKey = EnvironmentVariable(name: r'GEMINI_API_KEY'); -} diff --git a/examples/gemini/celest/lib/fix_data/v1_migration.yaml b/examples/gemini/celest/lib/fix_data/v1_migration.yaml new file mode 100644 index 00000000..280b9722 --- /dev/null +++ b/examples/gemini/celest/lib/fix_data/v1_migration.yaml @@ -0,0 +1,13 @@ +# Generated by Celest. Do not modify. +version: 1 +transforms: + - title: "Updates the import of the Celest client" + date: 2024-10-08 + element: + uris: ["client.dart"] + variable: celest + changes: + - kind: replacedBy + newElement: + uris: ["package:gemini_example_client/gemini_example_client.dart"] + variable: celest diff --git a/examples/gemini/celest/lib/src/client/functions.dart b/examples/gemini/celest/lib/src/client/functions.dart deleted file mode 100644 index 2d938e64..00000000 --- a/examples/gemini/celest/lib/src/client/functions.dart +++ /dev/null @@ -1,107 +0,0 @@ -// Generated by Celest. This file should not be modified manually, but -// it can be checked into version control. -// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import - -library; // ignore_for_file: no_leading_underscores_for_library_prefixes - -import 'dart:convert' as _$convert; - -import 'package:celest/celest.dart'; -import 'package:celest_core/src/exception/cloud_exception.dart'; -import 'package:celest_core/src/exception/serialization_exception.dart'; -import 'package:google_generative_ai/src/error.dart' as _$error; -import 'package:http/src/exception.dart' as _$exception; - -import '../../client.dart'; - -class CelestFunctions { - final gemini = CelestFunctionsGemini(); -} - -class CelestFunctionsGemini { - Never _throwError({ - required int $statusCode, - required Map $body, - }) { - final $error = ($body['error'] as Map); - final $code = ($error['code'] as String); - final $details = ($error['details'] as Map?); - switch ($code) { - case r'BadRequestException': - throw Serializers.instance.deserialize($details); - case r'UnauthorizedException': - throw Serializers.instance.deserialize($details); - case r'InternalServerError': - throw Serializers.instance.deserialize($details); - case r'SerializationException': - throw Serializers.instance - .deserialize($details); - case r'GenerativeAIException': - throw Serializers.instance - .deserialize<_$error.GenerativeAIException>($details); - case r'InvalidApiKey': - throw Serializers.instance.deserialize<_$error.InvalidApiKey>($details); - case r'UnsupportedUserLocation': - throw Serializers.instance - .deserialize<_$error.UnsupportedUserLocation>($details); - case r'ServerException': - throw Serializers.instance - .deserialize<_$error.ServerException>($details); - case r'ClientException': - throw Serializers.instance - .deserialize<_$exception.ClientException>($details); - case _: - switch ($statusCode) { - case 400: - throw BadRequestException($code); - case _: - throw InternalServerError($code); - } - } - } - - /// Returns a list of available models. - Future> availableModels() async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/gemini/available-models'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - ); - final $body = - (_$convert.jsonDecode($response.body) as Map); - if ($response.statusCode != 200) { - _throwError( - $statusCode: $response.statusCode, - $body: $body, - ); - } - return ($body['response'] as Iterable) - .map((el) => (el as String)) - .toList(); - } - - /// Prompts the Gemini [modelName] with the given [prompt] and [parameters]. - /// - /// Returns the generated text. - Future generateContent({ - required String modelName, - required String prompt, - }) async { - final $response = await celest.httpClient.post( - celest.baseUri.resolve('/gemini/generate-content'), - headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: _$convert.jsonEncode({ - r'modelName': modelName, - r'prompt': prompt, - }), - ); - final $body = - (_$convert.jsonDecode($response.body) as Map); - if ($response.statusCode != 200) { - _throwError( - $statusCode: $response.statusCode, - $body: $body, - ); - } - return ($body['response'] as String); - } -} diff --git a/examples/gemini/celest/lib/src/client/serializers.dart b/examples/gemini/celest/lib/src/client/serializers.dart deleted file mode 100644 index 57e6be77..00000000 --- a/examples/gemini/celest/lib/src/client/serializers.dart +++ /dev/null @@ -1,85 +0,0 @@ -// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:celest/celest.dart'; -import 'package:celest_core/src/exception/cloud_exception.dart'; -import 'package:celest_core/src/exception/serialization_exception.dart'; -import 'package:google_generative_ai/src/error.dart' as _$error; -import 'package:http/src/exception.dart' as _$exception; - -void initSerializers() { - Serializers.instance - .put(Serializer.define>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return BadRequestException(($serialized[r'message'] as String)); - }, - )); - Serializers.instance - .put(Serializer.define>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return InternalServerError(($serialized[r'message'] as String)); - }, - )); - Serializers.instance - .put(Serializer.define?>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return UnauthorizedException( - (($serialized?[r'message'] as String?)) ?? 'Unauthorized'); - }, - )); - Serializers.instance - .put(Serializer.define>( - serialize: ($value) => { - r'message': $value.message, - r'offset': $value.offset, - r'source': $value.source, - }, - deserialize: ($serialized) { - return SerializationException(($serialized[r'message'] as String)); - }, - )); - Serializers.instance.put( - Serializer.define<_$error.GenerativeAIException, Map>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return _$error.GenerativeAIException(($serialized[r'message'] as String)); - }, - )); - Serializers.instance - .put(Serializer.define<_$error.InvalidApiKey, Map>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return _$error.InvalidApiKey(($serialized[r'message'] as String)); - }, - )); - Serializers.instance - .put(Serializer.define<_$error.ServerException, Map>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return _$error.ServerException(($serialized[r'message'] as String)); - }, - )); - Serializers.instance.put( - Serializer.define<_$error.UnsupportedUserLocation, Map?>( - serialize: ($value) => {}, - deserialize: ($serialized) { - return _$error.UnsupportedUserLocation(); - }, - )); - Serializers.instance - .put(Serializer.define<_$exception.ClientException, Map>( - serialize: ($value) => { - r'message': $value.message, - r'uri': Serializers.instance.serialize($value.uri), - }, - deserialize: ($serialized) { - return _$exception.ClientException( - ($serialized[r'message'] as String), - Serializers.instance.deserialize($serialized[r'uri']), - ); - }, - )); -} diff --git a/examples/gemini/celest/functions/gemini.dart b/examples/gemini/celest/lib/src/functions/gemini.dart similarity index 81% rename from examples/gemini/celest/functions/gemini.dart rename to examples/gemini/celest/lib/src/functions/gemini.dart index 9b589c47..5cbecaad 100644 --- a/examples/gemini/celest/functions/gemini.dart +++ b/examples/gemini/celest/lib/src/functions/gemini.dart @@ -4,10 +4,9 @@ import 'dart:convert'; import 'package:celest/celest.dart'; +import 'package:celest_backend/src/project.dart'; import 'package:google_generative_ai/google_generative_ai.dart'; -import '../generated/resources.dart'; - /// Returns a list of available models. @cloud Future> availableModels() async => _availableModels; @@ -24,11 +23,11 @@ const _availableModels = [ /// /// Returns the generated text. @cloud -Future generateContent({ +Stream generateContent({ required String modelName, required String prompt, - @env.geminiApiKey required String apiKey, -}) async { + @geminiApiKey required String apiKey, +}) async* { if (!_availableModels.contains(modelName)) { throw BadRequestException('Invalid model: $modelName'); } @@ -39,16 +38,19 @@ Future generateContent({ ]; print('Sending prompt: $prompt'); - final response = await model.generateContent(request); - print('Got response: ${_prettyJson(response.toJson())}'); + await for (final response in model.generateContentStream(request)) { + print('Got response: ${_prettyJson(response.toJson())}'); - switch (response.text) { - case final text?: - print('Selected answer: $text'); - return text; - case _: - throw InternalServerError('Failed to generate content'); + switch (response.text) { + case final text?: + print(text); + yield text; + case _: + throw InternalServerError('Failed to generate content'); + } } + + print('Finished generating content'); } extension on GenerateContentResponse { diff --git a/examples/gemini/celest/generated/README.md b/examples/gemini/celest/lib/src/generated/README.md similarity index 100% rename from examples/gemini/celest/generated/README.md rename to examples/gemini/celest/lib/src/generated/README.md diff --git a/examples/gemini/celest/lib/src/generated/cloud.celest.dart b/examples/gemini/celest/lib/src/generated/cloud.celest.dart new file mode 100644 index 00000000..1025477e --- /dev/null +++ b/examples/gemini/celest/lib/src/generated/cloud.celest.dart @@ -0,0 +1,42 @@ +// Generated by Celest. This file should not be modified manually, but +// it can be checked into version control. +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import, deprecated_member_use + +library; + +import 'package:celest_backend/src/generated/config.celest.dart'; + +/// The interface to your Celest backend. +/// +/// Similar to the `celest` global in the frontend, this +/// provides access to the backend environment and services +/// configured for your project. +const CelestCloud celest = CelestCloud._(); + +/// The interface to your Celest backend. +/// +/// Similar to the `Celest` class in the frontend, this class +/// provides access to the backend environment and services +/// configured for your project. +class CelestCloud { + const CelestCloud._(); + + /// The current environment of the Celest service. + /// + /// This is determined by the `CELEST_ENVIRONMENT` environment variable + /// which is set for you by the deployment environment. + CelestEnvironment get currentEnvironment => + (env.currentEnvironment as CelestEnvironment); + + /// The environment variables for the Celest service. + /// + /// This class provides access to the environment variable values + /// that are configured for the [currentEnvironment]. + CelestEnvironmentVariables get env => const CelestEnvironmentVariables(); + + /// The secrets for the Celest service. + /// + /// This class provides access to the secret values that are configured + /// for the [currentEnvironment]. + CelestSecrets get secrets => const CelestSecrets(); +} diff --git a/examples/gemini/celest/lib/src/generated/config.celest.dart b/examples/gemini/celest/lib/src/generated/config.celest.dart new file mode 100644 index 00000000..01e982d0 --- /dev/null +++ b/examples/gemini/celest/lib/src/generated/config.celest.dart @@ -0,0 +1,56 @@ +// Generated by Celest. This file should not be modified manually, but +// it can be checked into version control. +// ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import, deprecated_member_use + +library; + +import 'package:celest/celest.dart'; +import 'package:celest/src/core/context.dart'; + +/// An environment of a deployed Celest service. +/// +/// Celest services can have multiple isolated branches, for example +/// a `development` and `production` environment. +extension type const CelestEnvironment._(String _env) + implements Environment, String { + /// The local Celest environment, used to delineate when a + /// Celest service is running on a developer machine as opposed + /// to the cloud. + static const CelestEnvironment local = CelestEnvironment._('local'); + + /// The production Celest environment which is common to all + /// Celest projects and labels the environment which is considered + /// live and served to end-users. + static const CelestEnvironment production = CelestEnvironment._('production'); + + /// Whether `this` represents the [local] environment. + bool get isLocal => this == local; + + /// Whether `this` represents the [production] environment. + bool get isProduction => this == production; +} + +/// The environment variables for the Celest service. +/// +/// This class provides access to the environment variable values +/// that are configured for the current [CelestEnvironment]. +class CelestEnvironmentVariables { + const CelestEnvironmentVariables(); + + /// The environment variable that determines the current environment. + /// + /// This is set by the deployment environment and is used to + /// determine the current environment of the Celest service. + String get currentEnvironment => context.expect(env.environment); +} + +/// The secrets for the Celest service. +/// +/// This class provides access to the secret values that are configured +/// for the current [CelestEnvironment]. +class CelestSecrets { + const CelestSecrets(); + + /// The value of the `GEMINI_API_KEY` secret. + String get geminiApiKey => context.expect(const env('GEMINI_API_KEY')); +} diff --git a/examples/gemini/celest/lib/src/project.dart b/examples/gemini/celest/lib/src/project.dart new file mode 100644 index 00000000..97e4aca8 --- /dev/null +++ b/examples/gemini/celest/lib/src/project.dart @@ -0,0 +1,7 @@ +import 'package:celest/celest.dart'; + +const geminiApiKey = secret('GEMINI_API_KEY'); + +const project = Project( + name: 'gemini_example', +); diff --git a/examples/gemini/celest/project.dart b/examples/gemini/celest/project.dart deleted file mode 100644 index 57653a34..00000000 --- a/examples/gemini/celest/project.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:celest/celest.dart'; - -const project = Project( - name: 'gemini_example', -); diff --git a/examples/gemini/celest/project.dart b/examples/gemini/celest/project.dart new file mode 120000 index 00000000..a0bad29d --- /dev/null +++ b/examples/gemini/celest/project.dart @@ -0,0 +1 @@ +/Users/dillonnys/celest/cloud/celest/examples/gemini/celest/lib/src/project.dart \ No newline at end of file diff --git a/examples/gemini/celest/pubspec.yaml b/examples/gemini/celest/pubspec.yaml index fcc43cea..c815c652 100644 --- a/examples/gemini/celest/pubspec.yaml +++ b/examples/gemini/celest/pubspec.yaml @@ -3,12 +3,12 @@ description: The Celest backend for celest_project. publish_to: none environment: - sdk: ^3.3.0 + sdk: ^3.4.0 dependencies: - celest: ^0.5.0-0 - google_generative_ai: ^0.2.0 - http: ">=0.13.0 <2.0.0" + celest: '^1.0.0-0' + google_generative_ai: '^0.2.0' + http: '>=0.13.0 <2.0.0' dependency_overrides: celest: @@ -21,5 +21,5 @@ dependency_overrides: path: ../../../packages/celest_core dev_dependencies: - lints: ^4.0.0 - test: ^1.25.0 + lints: '^4.0.0' + test: '^1.25.0' diff --git a/examples/gemini/ios/Podfile b/examples/gemini/ios/Podfile index d97f17e2..ebaf3182 100644 --- a/examples/gemini/ios/Podfile +++ b/examples/gemini/ios/Podfile @@ -1,5 +1,4 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/examples/gemini/ios/Podfile.lock b/examples/gemini/ios/Podfile.lock new file mode 100644 index 00000000..e854763f --- /dev/null +++ b/examples/gemini/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - Flutter (1.0.0) + - native_authentication (0.0.1): + - Flutter + - objective_c (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - native_authentication (from `.symlinks/plugins/native_authentication/ios`) + - objective_c (from `.symlinks/plugins/objective_c/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + native_authentication: + :path: ".symlinks/plugins/native_authentication/ios" + objective_c: + :path: ".symlinks/plugins/objective_c/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + native_authentication: 694c13e929d3623caaa1183461d4ed1e1b14e6e7 + objective_c: 77e887b5ba1827970907e10e832eec1683f3431d + +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 + +COCOAPODS: 1.15.2 diff --git a/examples/gemini/ios/Runner.xcodeproj/project.pbxproj b/examples/gemini/ios/Runner.xcodeproj/project.pbxproj index 5e99f5c4..6327ff30 100644 --- a/examples/gemini/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/gemini/ios/Runner.xcodeproj/project.pbxproj @@ -10,7 +10,9 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 65D48D5181B2F444870AB149 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AC2421D3823B268E17F4AD7 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 85F52A22A1CD6EF5C9BECD37 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 835981513BE338A1ABDF7E76 /* Pods_RunnerTests.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -42,12 +44,16 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 178DD3F906F532C2AEC09103 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 2C7180C17A83D5C8A7E1FFD8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7FD14E6A910ED38FBB54F41E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 835981513BE338A1ABDF7E76 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,6 +61,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AC2421D3823B268E17F4AD7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C0F2A7CFA1C455BED657F273 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D1246C97FD2C65D34CB4DBB0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + FD3187504153347FAE151590 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,12 +72,35 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 65D48D5181B2F444870AB149 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C32BDC7F0D82F6305A725885 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 85F52A22A1CD6EF5C9BECD37 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2BC6FC86530968D3A067ACDE /* Pods */ = { + isa = PBXGroup; + children = ( + FD3187504153347FAE151590 /* Pods-Runner.debug.xcconfig */, + 2C7180C17A83D5C8A7E1FFD8 /* Pods-Runner.release.xcconfig */, + 7FD14E6A910ED38FBB54F41E /* Pods-Runner.profile.xcconfig */, + 178DD3F906F532C2AEC09103 /* Pods-RunnerTests.debug.xcconfig */, + C0F2A7CFA1C455BED657F273 /* Pods-RunnerTests.release.xcconfig */, + D1246C97FD2C65D34CB4DBB0 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -76,6 +109,15 @@ path = RunnerTests; sourceTree = ""; }; + 46AA02C8700DDED9DE52BF11 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9AC2421D3823B268E17F4AD7 /* Pods_Runner.framework */, + 835981513BE338A1ABDF7E76 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 2BC6FC86530968D3A067ACDE /* Pods */, + 46AA02C8700DDED9DE52BF11 /* Frameworks */, ); sourceTree = ""; }; @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + A5BFED0BAB8C8C8B27279B48 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + C32BDC7F0D82F6305A725885 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + C1D948841684376CF81A8E3F /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + FEDFB5D39F1A78E982D33FBF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -253,6 +301,67 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A5BFED0BAB8C8C8B27279B48 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C1D948841684376CF81A8E3F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FEDFB5D39F1A78E982D33FBF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -379,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 178DD3F906F532C2AEC09103 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = C0F2A7CFA1C455BED657F273 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D1246C97FD2C65D34CB4DBB0 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/examples/gemini/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/gemini/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/examples/gemini/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/examples/gemini/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/examples/gemini/lib/main.dart b/examples/gemini/lib/main.dart index 6985f87c..a14881d9 100644 --- a/examples/gemini/lib/main.dart +++ b/examples/gemini/lib/main.dart @@ -1,8 +1,10 @@ +import 'dart:async'; import 'dart:developer'; -// Import the generated Celest client -import 'package:celest_backend/client.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +// Import the generated Celest client +import 'package:gemini_example_client/gemini_example_client.dart'; void main() { // Initializes Celest in your Flutter app @@ -19,8 +21,9 @@ class GeminiApp extends StatefulWidget { class _GeminiAppState extends State { // Controllers for the text fields. - final _questionController = TextEditingController(); + final _promptController = TextEditingController(); final _answerController = TextEditingController(); + final _answerScrollController = ScrollController(); /// The selected model to use. /// @@ -29,28 +32,54 @@ class _GeminiAppState extends State { late String _selectedModel; /// Whether [_availableModelsFuture] has completed and the available models - /// have been loaded fromthe backend. + /// have been loaded from the backend. var _loadedAvailableModels = false; final _availableModelsFuture = celest.functions.gemini.availableModels(); + StreamSubscription? _contentStreamSub; + + @override + void dispose() { + _availableModelsFuture.ignore(); + _contentStreamSub?.cancel(); + _contentStreamSub = null; + _promptController.dispose(); + _answerController.dispose(); + _answerScrollController.dispose(); + super.dispose(); + } + /// Sends the prompt request to the backend and updates the UI with the - /// response. - Future _sendGeminiRequest() async { - try { - final response = await celest.functions.gemini.generateContent( - prompt: _questionController.text, - modelName: _selectedModel, + /// response as it streams in. + void _sendGeminiRequest() { + final contentStream = celest.functions.gemini.generateContent( + prompt: _promptController.text, + modelName: _selectedModel, + ); + setState(() { + _answerController.text = ''; + _contentStreamSub?.cancel(); + _contentStreamSub = contentStream.listen( + (response) { + log(response); + setState(() => _answerController.text += response); + SchedulerBinding.instance.addPostFrameCallback((_) { + _answerScrollController.jumpTo( + _answerScrollController.position.maxScrollExtent, + ); + }); + }, + onError: (error, stackTrace) { + log( + 'Failed to generate content', + error: error, + stackTrace: stackTrace, + ); + setState(() => _answerController.text = '$error'); + }, + onDone: () => log('Finished generating content'), ); - log(response); - setState(() { - _answerController.text = response; - }); - } on Exception catch (e) { - log('Failed to generate content', error: e); - setState(() { - _answerController.text = '$e'; - }); - } + }); } /// Builds a dropdown menu with the available models loaded from the backend. @@ -89,70 +118,71 @@ class _GeminiAppState extends State { debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( - title: const Text('Gemini Prompt'), + title: const Text('Gemini AI'), ), body: SingleChildScrollView( - child: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 100), - Text( - 'GPT Settings', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 20), - // Fetches available models from the backend to populate a - // dropdown menu. - FutureBuilder>( - future: _availableModelsFuture, - builder: (context, snapshot) => switch (snapshot) { - // Build the dropdown when data is available - AsyncSnapshot(:final data?) => - _availableModelsDropdown(data), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Fetches available models from the backend to populate a + // dropdown menu. + FutureBuilder>( + future: _availableModelsFuture, + builder: (context, snapshot) => switch (snapshot) { + // Build the dropdown when data is available + AsyncSnapshot(:final data?) => + _availableModelsDropdown(data), - // Handle the error case - AsyncSnapshot(:final error?) => Text('$error'), + // Handle the error case + AsyncSnapshot(:final error?) => Text('$error'), - // If waiting, show a progress indicator - _ => const CircularProgressIndicator(), - }, - ), - const SizedBox(height: 20), - TextField( - maxLines: 10, - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'Enter your question here', + // If waiting, show a progress indicator + _ => const CircularProgressIndicator(), + }, ), - controller: _questionController, - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: - !_loadedAvailableModels ? null : _sendGeminiRequest, - child: const Text('Ask your question!'), - ), - const SizedBox(height: 50), - Text( - 'Answer', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 20), - TextField( - readOnly: true, - maxLines: 10, - decoration: const InputDecoration( - border: OutlineInputBorder(), - hintText: 'Your answer will go here', + const SizedBox(width: 20), + Expanded( + child: TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'What is the meaning of life?', + ), + controller: _promptController, + ), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: + !_loadedAvailableModels ? null : _sendGeminiRequest, + child: const Text('Prompt'), ), - controller: _answerController, + ], + ), + const SizedBox(height: 50), + Text( + 'Answer', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 20), + TextField( + readOnly: true, + showCursor: false, + canRequestFocus: false, + maxLines: 15, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'The response will be streamed here', ), - ], - ), + scrollController: _answerScrollController, + controller: _answerController, + ), + ], ), ), ), diff --git a/examples/gemini/macos/Podfile b/examples/gemini/macos/Podfile index c795730d..b52666a1 100644 --- a/examples/gemini/macos/Podfile +++ b/examples/gemini/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/examples/gemini/macos/Podfile.lock b/examples/gemini/macos/Podfile.lock new file mode 100644 index 00000000..8961970d --- /dev/null +++ b/examples/gemini/macos/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - FlutterMacOS (1.0.0) + - native_authentication (0.0.1): + - FlutterMacOS + - objective_c (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - native_authentication (from `Flutter/ephemeral/.symlinks/plugins/native_authentication/macos`) + - objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + native_authentication: + :path: Flutter/ephemeral/.symlinks/plugins/native_authentication/macos + objective_c: + :path: Flutter/ephemeral/.symlinks/plugins/objective_c/macos + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + native_authentication: cf7783d5074db2e2e3babf81b12e14da700f0bcd + objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067 + +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 + +COCOAPODS: 1.15.2 diff --git a/examples/gemini/macos/Runner.xcodeproj/project.pbxproj b/examples/gemini/macos/Runner.xcodeproj/project.pbxproj index b168b8e1..7891de65 100644 --- a/examples/gemini/macos/Runner.xcodeproj/project.pbxproj +++ b/examples/gemini/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 713479DDFAB01A62E804C931 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FB77EE4968521D8F600BAE7 /* Pods_RunnerTests.framework */; }; + D76511B2FB2ACE58F9FA9E7B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 119485A69B7FA63383E9E0CF /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0DAEF17E268BB893156834E5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 119485A69B7FA63383E9E0CF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1628610642D2514F8EA64536 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 24503C59919D7B2F75CDE45E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* gemini.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gemini.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* gemini.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gemini.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +82,12 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 6FB77EE4968521D8F600BAE7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 78FCBB0D037A4E46D0B88E1C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B289EF46C3B8C2A447CECA22 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + CA0653ABA6D1178587D8135C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 713479DDFAB01A62E804C931 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D76511B2FB2ACE58F9FA9E7B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 9FD0358DA2533E631123A9CB /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 9FD0358DA2533E631123A9CB /* Pods */ = { + isa = PBXGroup; + children = ( + B289EF46C3B8C2A447CECA22 /* Pods-Runner.debug.xcconfig */, + 1628610642D2514F8EA64536 /* Pods-Runner.release.xcconfig */, + 78FCBB0D037A4E46D0B88E1C /* Pods-Runner.profile.xcconfig */, + CA0653ABA6D1178587D8135C /* Pods-RunnerTests.debug.xcconfig */, + 24503C59919D7B2F75CDE45E /* Pods-RunnerTests.release.xcconfig */, + 0DAEF17E268BB893156834E5 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 119485A69B7FA63383E9E0CF /* Pods_Runner.framework */, + 6FB77EE4968521D8F600BAE7 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 73664990266B101F09927B0A /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + EBD5C8BBB8C84EE6A8982783 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 727A2FECF3B1E1E7E3AC150A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -329,6 +361,67 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 727A2FECF3B1E1E7E3AC150A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 73664990266B101F09927B0A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EBD5C8BBB8C84EE6A8982783 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = CA0653ABA6D1178587D8135C /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 24503C59919D7B2F75CDE45E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0DAEF17E268BB893156834E5 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/examples/gemini/macos/Runner.xcworkspace/contents.xcworkspacedata b/examples/gemini/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/examples/gemini/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/examples/gemini/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/examples/gemini/macos/Runner/AppDelegate.swift b/examples/gemini/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100644 --- a/examples/gemini/macos/Runner/AppDelegate.swift +++ b/examples/gemini/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/gemini/pubspec.yaml b/examples/gemini/pubspec.yaml index f4dc515d..69ff0552 100644 --- a/examples/gemini/pubspec.yaml +++ b/examples/gemini/pubspec.yaml @@ -1,5 +1,5 @@ name: gemini -description: "A new Flutter project." +description: An example of how to use Gemini AI from Celest. publish_to: 'none' version: 0.1.0 @@ -8,10 +8,11 @@ environment: flutter: ">=3.19.0" dependencies: - celest_backend: - path: celest/ flutter: sdk: flutter + gemini_example_client: + path: celest/client/ + stream_transform: ^2.1.0 dependency_overrides: celest: diff --git a/examples/gemini/web/index.html b/examples/gemini/web/index.html index b02ec6a4..030576a0 100644 --- a/examples/gemini/web/index.html +++ b/examples/gemini/web/index.html @@ -18,42 +18,21 @@ - + - + - gemini + Celest + Gemini AI - - - - - +