Skip to content

Commit

Permalink
Adding Generative AI Features to DartPad (#3135)
Browse files Browse the repository at this point in the history
* WIP: generate new snippet

* WIP: gemini

* added Suggest Fix from AI feature

* feature: generate code

* TODOs

* refactoring the generative model instructions and filtering out stray Markdown code blocks

* tweaks to the API key management

* test and dep tweaks

* Fix issues after merge with main branch

* Add suggestFix

* initial support for streaming response

* used fetch_client on the web to enable client-side streaming results in chunks

* adding note about why we're using fetch_client

* replace NOTEs with TODOs when bug fixes lang

* cleanup before refactoring PromptDialog

* streaming code - end-to-end

* show generated code as it's generated

* refactor to make suggestFix streaming

* tweak

* streaming fix suggestions

* show the OK buttons as disabled when it's not time to press them

* a little bit of UI cleanup and code refactoring

* migrated to http v1.3.0 and removing FetchClient

* UI tweak

* tweaks

* added support for updating existing code

* end-to-end support for image attachments

* added a max attachment number we can adjust later (started at 3)

* updated to show items in true reverse order in the attachment list

* image attachment zoom

* tweak

* add cmd+enter and ctrl+enter to the prompt dialog

* run grind build-storage-artifacts

* fixed updateCode and other tweaks

* gemini menu updates

* dialog polish

* fixed where the Gemini tagline goes

* give the dialogs edges in dark and light mode

* disable gzip to enable client to get content as it's available

* limiting packages in system instructions; added fix suggestions to the console

* system instructions tweak

* adding a gzip not

* enable auto-run from the generate code dialog

* show quick fixes button next to the suggest fix button if analyzer says there's a fix for an issue

* remove unneeded editor services method

* added diff on update or suggest fix

* fixed an async scrolling issue

* console crash and dark mode diff view support

* another attempt to fix the console scroll crash; fixup ReadOnlyDiffWidget for keyboard shortcuts

* tweak

* added preset prompt buttons and a means for saving and restoring the user's last prompt

* show quick fix action button (lightbulb) next to suggest fix action button (spark) in each error line in the problems area.

* WIP: generate/update code based on Flutter | Dart

* allow the user to decide between Dart and Flutter when generating and update code

* tweak

* license header fixes

* review feedback updates

* updated model to released gemini-2.0-flash

* gen-ai build/run-time switch

* simplified gen-ai feature enablement

* Update .gitignore

Co-authored-by: Kevin Moore <kevmoo@users.noreply.github.com>

* Update pkgs/dart_services/lib/src/common_server.dart

Co-authored-by: Kevin Moore <kevmoo@users.noreply.github.com>

* fix compile error

* updated system instructions to prevent spurious output

* update stable deps

* WIP: applying feedback

* workaround for #3148 that shows/hides the Suggest Fix button in the console based on the error state of the app

* set appType in Update Code dialog based on current code; add separate prompts for Dart/Flutter.

* use appType in suggest fix as well; avoid Dart programs being turned into Flutter programs

* fixed focus such that Cmd+Enter/Ctrl+Enter work on Prompt and Generate dialogs

* updated cleanCode to assume code inside markdown dart code block

* updated based on copilot feedback

* fixing conflicts

* fixing another conflict

* fixing another conflict?

---------

Co-authored-by: John Ryan <ryjohn@google.com>
Co-authored-by: Kevin Moore <kevmoo@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 28, 2025
1 parent 15265fe commit cca2502
Show file tree
Hide file tree
Showing 26 changed files with 2,025 additions and 27 deletions.
97 changes: 97 additions & 0 deletions pkgs/dart_services/lib/src/common_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:shelf_router/shelf_router.dart';
import 'analysis.dart';
import 'caching.dart';
import 'compiling.dart';
import 'generative_ai.dart';
import 'project_templates.dart';
import 'pub.dart';
import 'sdk.dart';
Expand All @@ -36,6 +37,7 @@ class CommonServerImpl {

late Analyzer analyzer;
late Compiler compiler;
final ai = GenerativeAI();

CommonServerImpl(
this.sdk,
Expand Down Expand Up @@ -85,6 +87,9 @@ class CommonServerApi {
router.post(r'/api/<apiVersion>/format', handleFormat);
router.post(r'/api/<apiVersion>/document', handleDocument);
router.post(r'/api/<apiVersion>/openInIDX', handleOpenInIdx);
router.post(r'/api/<apiVersion>/generateCode', generateCode);
router.post(r'/api/<apiVersion>/updateCode', updateCode);
router.post(r'/api/<apiVersion>/suggestFix', suggestFix);
return router;
}();

Expand Down Expand Up @@ -264,6 +269,98 @@ class CommonServerApi {
}
}

@Route.post('$apiPrefix/suggestFix')
Future<Response> suggestFix(Request request, String apiVersion) async {
if (apiVersion != api3) return unhandledVersion(apiVersion);

final suggestFixRequest =
api.SuggestFixRequest.fromJson(await request.readAsJson());

return _streamResponse(
'suggestFix',
impl.ai.suggestFix(
appType: suggestFixRequest.appType,
message: suggestFixRequest.errorMessage,
line: suggestFixRequest.line,
column: suggestFixRequest.column,
source: suggestFixRequest.source,
),
);
}

@Route.post('$apiPrefix/generateCode')
Future<Response> generateCode(Request request, String apiVersion) async {
if (apiVersion != api3) return unhandledVersion(apiVersion);

final generateCodeRequest =
api.GenerateCodeRequest.fromJson(await request.readAsJson());

return _streamResponse(
'generateCode',
impl.ai.generateCode(
appType: generateCodeRequest.appType,
prompt: generateCodeRequest.prompt,
attachments: generateCodeRequest.attachments,
),
);
}

@Route.post('$apiPrefix/updateCode')
Future<Response> updateCode(Request request, String apiVersion) async {
if (apiVersion != api3) return unhandledVersion(apiVersion);

final updateCodeRequest =
api.UpdateCodeRequest.fromJson(await request.readAsJson());

return _streamResponse(
'updateCode',
impl.ai.updateCode(
appType: updateCodeRequest.appType,
prompt: updateCodeRequest.prompt,
source: updateCodeRequest.source,
attachments: updateCodeRequest.attachments,
),
);
}

Future<Response> _streamResponse(
String action,
Stream<String> inputStream,
) async {
try {
// NOTE: disabling gzip so that the client gets the data in the same
// chunks that the LLM is providing it to us. With gzip, the client
// receives the data all at once at the end of the stream.
final outputStream = inputStream.transform(utf8.encoder);
return Response.ok(
outputStream,
headers: {
'Content-Type': 'text/plain; charset=utf-8', // describe our bytes
'Content-Encoding': 'identity', // disable gzip
},
context: {'shelf.io.buffer_output': false}, // disable buffering
);
} catch (e, stackTrace) {
final logger = Logger(action);
logger.severe('Error during $action operation: $e', e, stackTrace);

String errorMessage;
if (e is TimeoutException) {
errorMessage = 'Operation timed out while processing $action request.';
} else if (e is FormatException) {
errorMessage = 'Invalid format in $action request: $e';
} else if (e is IOException) {
errorMessage = 'I/O error occurred during $action operation: $e';
} else {
errorMessage = 'Failed to process $action request. Error: $e';
}

return Response.internalServerError(
body: errorMessage,
);
}
}

Response ok(Map<String, dynamic> json) {
return Response.ok(
_jsonEncoder.convert(json),
Expand Down
Loading

0 comments on commit cca2502

Please sign in to comment.