Skip to content

Commit 5b55dce

Browse files
authored
Call genui when url param 'genui=true' is passed. (#3188)
1 parent bbcae7b commit 5b55dce

File tree

8 files changed

+137
-53
lines changed

8 files changed

+137
-53
lines changed

pkgs/dart_services/README.md

+16-7
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,23 @@ dart bin/server.dart
2929
The server will run from port 8080 and export several JSON APIs, like
3030
`/api/v3/analyze` and `/api/v3/compile`.
3131

32-
To test Gemini API features on a local machine,
33-
get an API key from [Google AI Studio](https://aistudio.google.com)
34-
and set the `GEMINI_API_KEY` environment variable before running:
3532

36-
```
37-
export GEMINI_API_KEY=<YOUR_API_KEY>
38-
dart bin/server.dart
39-
```
33+
### Enabling code generation
34+
35+
To test code generation features locally:
36+
37+
1. Get needed API keys:
38+
39+
* Get a GEMINI_API_KEY key from [Google AI Studio](https://aistudio.google.com)
40+
* See how to get a GENUI_API_KEY in the internal go/dartpad-manual.
41+
42+
2. Set the needed environment variables before running:
43+
44+
```
45+
export GEMINI_API_KEY=<YOUR_GEMINI_API_KEY>
46+
export GENUI_API_KEY=<YOUR_GENUI_API_KEY>
47+
dart bin/server.dart
48+
```
4049

4150
### Testing
4251

pkgs/dart_services/lib/server.dart

+4-26
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ Future<void> main(List<String> args) async {
3434
'The name of the Cloud Storage bucket for compilation artifacts.',
3535
defaultsTo: 'nnbd_artifacts',
3636
)
37-
..addOption(
38-
'genui-key',
39-
valueHelp: 'key',
40-
help:
41-
'Genui api key to be passed with request. '
42-
'Not needed for hosted instance, just for local development.',
43-
defaultsTo: null,
44-
)
4537
..addFlag(
4638
'help',
4739
abbr: 'h',
@@ -79,7 +71,6 @@ Future<void> main(List<String> args) async {
7971
final redisServerUri = results['redis-url'] as String?;
8072
final storageBucket =
8173
results['storage-bucket'] as String? ?? 'nnbd_artifacts';
82-
final genUiKey = results['genui-key'] as String?;
8374

8475
final cloudRunEnvVars = Platform.environment.entries
8576
.where((entry) => entry.key.startsWith('K_'))
@@ -100,7 +91,6 @@ Starting dart-services:
10091
sdk,
10192
redisServerUri,
10293
storageBucket,
103-
genUiKey: genUiKey,
10494
);
10595

10696
_logger.info('Listening on port ${server.port}');
@@ -111,14 +101,12 @@ class EndpointsServer {
111101
int port,
112102
Sdk sdk,
113103
String? redisServerUri,
114-
String storageBucket, {
115-
String? genUiKey,
116-
}) async {
104+
String storageBucket,
105+
) async {
117106
final endpointsServer = EndpointsServer._(
118107
sdk,
119108
redisServerUri,
120109
storageBucket,
121-
genUiKey: genUiKey,
122110
);
123111
await endpointsServer._init();
124112

@@ -137,12 +125,7 @@ class EndpointsServer {
137125

138126
late final CommonServerApi commonServer;
139127

140-
EndpointsServer._(
141-
Sdk sdk,
142-
String? redisServerUri,
143-
String storageBucket, {
144-
String? genUiKey,
145-
}) {
128+
EndpointsServer._(Sdk sdk, String? redisServerUri, String storageBucket) {
146129
// The name of the Cloud Run revision being run, for more detail please see:
147130
// https://cloud.google.com/run/docs/reference/container-contract#env-vars
148131
final serverVersion = Platform.environment['K_REVISION'];
@@ -153,12 +136,7 @@ class EndpointsServer {
153136
: RedisCache(redisServerUri, sdk, serverVersion);
154137

155138
commonServer = CommonServerApi(
156-
CommonServerImpl(
157-
sdk,
158-
cache,
159-
storageBucket: storageBucket,
160-
genUiKey: genUiKey,
161-
),
139+
CommonServerImpl(sdk, cache, storageBucket: storageBucket),
162140
);
163141

164142
final pipeline = const Pipeline()

pkgs/dart_services/lib/src/common_server.dart

+10-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:shelf_router/shelf_router.dart';
1515
import 'analysis.dart';
1616
import 'caching.dart';
1717
import 'compiling.dart';
18+
import 'flutter_genui.dart';
1819
import 'generative_ai.dart';
1920
import 'project_templates.dart';
2021
import 'pub.dart';
@@ -38,12 +39,12 @@ class CommonServerImpl {
3839
late Analyzer analyzer;
3940
late Compiler compiler;
4041
final ai = GenerativeAI();
42+
final GenUi genui = GenUi();
4143

4244
CommonServerImpl(
4345
this.sdk,
4446
this.cache, {
4547
this.storageBucket = 'nnbd_artifacts',
46-
genUiKey,
4748
});
4849

4950
Future<void> init() async {
@@ -91,6 +92,7 @@ class CommonServerApi {
9192
router.post(r'/api/<apiVersion>/document', handleDocument);
9293
router.post(r'/api/<apiVersion>/openInIDX', handleOpenInIdx);
9394
router.post(r'/api/<apiVersion>/generateCode', generateCode);
95+
router.post(r'/api/<apiVersion>/generateUi', generateUi);
9496
router.post(r'/api/<apiVersion>/updateCode', updateCode);
9597
router.post(r'/api/<apiVersion>/suggestFix', suggestFix);
9698
return router;
@@ -329,20 +331,16 @@ class CommonServerApi {
329331
Future<Response> generateUi(Request request, String apiVersion) async {
330332
if (apiVersion != api3) return unhandledVersion(apiVersion);
331333

332-
final generateCodeRequest = api.GenerateCodeRequest.fromJson(
334+
final generateUiRequest = api.GenerateUiRequest.fromJson(
333335
await request.readAsJson(),
334336
);
335337

336-
return _streamResponse(
337-
'generateUi',
338-
Stream.fromIterable([
339-
'hello',
340-
' from',
341-
' genui',
342-
' for ',
343-
generateCodeRequest.prompt,
344-
]),
345-
);
338+
final resultStream = Stream.fromIterable([
339+
await impl.genui.generateCode(prompt: generateUiRequest.prompt),
340+
]);
341+
342+
// TODO(polina-c): setup better streaming
343+
return _streamResponse('generateUi', resultStream);
346344
}
347345

348346
Future<Response> updateCode(Request request, String apiVersion) async {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:io';
7+
8+
import 'package:http/http.dart' as http;
9+
import 'package:logging/logging.dart';
10+
11+
final _logger = Logger('genui');
12+
13+
class GenUi {
14+
static const _apiKeyVarName = 'GENUI_API_KEY';
15+
late final String _genuiApiKey;
16+
17+
GenUi() {
18+
_genuiApiKey = Platform.environment[_apiKeyVarName] ?? '';
19+
if (_genuiApiKey.isEmpty) {
20+
_logger.warning('$_apiKeyVarName not set; genui features DISABLED');
21+
} else {
22+
_logger.info('$_apiKeyVarName set; genui features ENABLED');
23+
}
24+
}
25+
26+
Future<String> generateCode({required String prompt}) async {
27+
final response = await _requestGenUI(prompt: prompt);
28+
29+
if (response.statusCode != 200) {
30+
throw Exception('Failed to generate ui: ${response.body}');
31+
}
32+
33+
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
34+
final flutterCode = decoded['flutterCode'] as String;
35+
36+
return flutterCode;
37+
}
38+
39+
Future<http.Response> _requestGenUI({required String prompt}) async {
40+
if (_genuiApiKey.isEmpty) {
41+
throw Exception('Missing environment variable: $_apiKeyVarName');
42+
}
43+
44+
return http.post(
45+
Uri.parse(
46+
'https://autopush-devgenui.sandbox.googleapis.com/v1beta1/firstparty/generateidecode?key=$_genuiApiKey',
47+
),
48+
headers: <String, String>{
49+
'Content-Type': 'application/json; charset=UTF-8',
50+
},
51+
body: jsonEncode(<String, String>{
52+
'userPrompt': prompt,
53+
'modelUrl': 'genuigemini://models/gemini-2.0-flash',
54+
}),
55+
);
56+
}
57+
}

pkgs/dartpad_shared/lib/services.dart

+9-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class ServicesClient {
6363
Stream<String> generateCode(GenerateCodeRequest request) =>
6464
_requestPostStream('generateCode', request.toJson());
6565

66+
Stream<String> generateUi(GenerateUiRequest request) =>
67+
_requestPostStream('generateUi', request.toJson());
68+
6669
Stream<String> updateCode(UpdateCodeRequest request) =>
6770
_requestPostStream('updateCode', request.toJson());
6871

@@ -120,7 +123,12 @@ class ServicesClient {
120123
httpRequest.body = json.encode(request);
121124
final response = await client.send(httpRequest);
122125

123-
if (response.statusCode != 200) throw ApiRequestError(action, '');
126+
if (response.statusCode != 200) {
127+
throw ApiRequestError(
128+
action,
129+
'${response.statusCode}: ${response.reasonPhrase}',
130+
);
131+
}
124132

125133
try {
126134
yield* response.stream.transform(utf8.decoder);

pkgs/dartpad_ui/lib/enable_gen_ai.dart

+15
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,18 @@
44

55
// Turn on or off gen-ai features in the client.
66
const bool genAiEnabled = false;
7+
8+
/*
9+
10+
To use GenUI locally:
11+
12+
1. Set genAiEnabled to true above.
13+
14+
2. See go/dartpad-manual-genui for instructions on how to start backend with GENUI_API_KEY.
15+
16+
3. Use this command to run UI:
17+
18+
flutter run -d chrome --web-port 8888 --web-browser-flag "--disable-web-security" \
19+
--web-launch-url="http://localhost:8888/?channel=localhost&genui=true"
20+
21+
*/

pkgs/dartpad_ui/lib/main.dart

+22-7
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class _DartPadAppState extends State<DartPadApp> {
130130
final channelParam = state.uri.queryParameters['channel'];
131131
final embedMode = state.uri.queryParameters['embed'] == 'true';
132132
final runOnLoad = state.uri.queryParameters['run'] == 'true';
133+
final useGenui = state.uri.queryParameters['genui'] == 'true';
133134

134135
return DartPadMainPage(
135136
initialChannel: channelParam,
@@ -139,6 +140,7 @@ class _DartPadAppState extends State<DartPadApp> {
139140
builtinSampleId: builtinSampleId,
140141
flutterSampleId: flutterSampleId,
141142
handleBrightnessChanged: handleBrightnessChanged,
143+
useGenui: useGenui,
142144
);
143145
}
144146

@@ -206,6 +208,7 @@ class DartPadMainPage extends StatefulWidget {
206208
final String? gistId;
207209
final String? builtinSampleId;
208210
final String? flutterSampleId;
211+
final bool useGenui;
209212

210213
DartPadMainPage({
211214
required this.initialChannel,
@@ -215,6 +218,7 @@ class DartPadMainPage extends StatefulWidget {
215218
this.gistId,
216219
this.builtinSampleId,
217220
this.flutterSampleId,
221+
this.useGenui = false,
218222
}) : super(
219223
key: ValueKey(
220224
'sample:$builtinSampleId gist:$gistId flutter:$flutterSampleId',
@@ -298,6 +302,10 @@ class _DartPadMainPageState extends State<DartPadMainPage>
298302
}
299303
});
300304
appModel.compilingState.addListener(_handleRunStarted);
305+
306+
debugPrint(
307+
'initialized: useGenui = ${widget.useGenui}, channel = $channel.',
308+
);
301309
}
302310

303311
@override
@@ -697,13 +705,20 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget {
697705
LocalStorage.instance.saveLastCreateCodePrompt(promptResponse.prompt);
698706

699707
try {
700-
final stream = appServices.generateCode(
701-
GenerateCodeRequest(
702-
appType: promptResponse.appType,
703-
prompt: promptResponse.prompt,
704-
attachments: promptResponse.attachments,
705-
),
706-
);
708+
final Stream<String> stream;
709+
if (widget.useGenui) {
710+
stream = appServices.generateUi(
711+
GenerateUiRequest(prompt: promptResponse.prompt),
712+
);
713+
} else {
714+
stream = appServices.generateCode(
715+
GenerateCodeRequest(
716+
appType: promptResponse.appType,
717+
prompt: promptResponse.prompt,
718+
attachments: promptResponse.attachments,
719+
),
720+
);
721+
}
707722

708723
final generateResponse = await showDialog<String>(
709724
context: context,

pkgs/dartpad_ui/lib/model.dart

+4
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ class AppServices {
464464
return services.generateCode(request);
465465
}
466466

467+
Stream<String> generateUi(GenerateUiRequest request) {
468+
return services.generateUi(request);
469+
}
470+
467471
Stream<String> updateCode(UpdateCodeRequest request) {
468472
return services.updateCode(request);
469473
}

0 commit comments

Comments
 (0)