Skip to content

POC for AI UI generation by syed. #769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 18 additions & 73 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,81 +1,26 @@
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'models/models.dart';
import 'providers/providers.dart';
import 'services/services.dart';
import 'consts.dart';
import 'app.dart';
import 'mock_schema.dart';
import 'ui_renderer.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
var settingsModel = await getSettingsFromSharedPrefs();
final initStatus = await initApp(
kIsDesktop,
settingsModel: settingsModel,
);
if (kIsDesktop) {
await initWindow(settingsModel: settingsModel);
}
if (!initStatus) {
settingsModel = settingsModel?.copyWithPath(workspaceFolderPath: null);
}

runApp(
ProviderScope(
overrides: [
settingsProvider.overrideWith(
(ref) => ThemeStateNotifier(settingsModel: settingsModel),
)
],
child: const DashApp(),
),
);
void main() {
runApp(const MyApp());
}

Future<bool> initApp(
bool initializeUsingPath, {
SettingsModel? settingsModel,
}) async {
GoogleFonts.config.allowRuntimeFetching = false;
try {
debugPrint("initializeUsingPath: $initializeUsingPath");
debugPrint("workspaceFolderPath: ${settingsModel?.workspaceFolderPath}");
final openBoxesStatus = await initHiveBoxes(
initializeUsingPath,
settingsModel?.workspaceFolderPath,
);
debugPrint("openBoxesStatus: $openBoxesStatus");
if (openBoxesStatus) {
await autoClearHistory(settingsModel: settingsModel);
}
return openBoxesStatus;
} catch (e) {
debugPrint("initApp failed due to $e");
return false;
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});

Future<void> initWindow({
Size? sz,
SettingsModel? settingsModel,
}) async {
if (kIsLinux) {
await setupInitialWindow(
sz: sz ?? settingsModel?.size,
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: const Text("AI UI Generator POC")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: DynamicTable(schema: mockSchema, data: mockData),

),
),
);
}
if (kIsMacOS || kIsWindows) {
if (sz != null) {
await setupWindow(
sz: sz,
off: const Offset(100, 100),
);
} else {
await setupWindow(
sz: settingsModel?.size,
off: settingsModel?.offset,
);
}
}
}
46 changes: 46 additions & 0 deletions lib/mock_schema.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const Map<String, dynamic> mockSchema = {
"layout": "",
"columns": [
{ "field": "name", "type": "text" },
{ "field": "age", "type": "chart" },
{ "field": "active", "type": "toggle" },
{ "field": "status", "type": "dropdown" },
{ "field": "joinedAt", "type": "date" },
{ "field": "score", "type": "slider" }
]
};

const List<Map<String, dynamic>> mockData = [
{
"name": "Alice",
"age": 30,
"active": true,
"status": "pending",
"joinedAt": "2023-01-01",
"score": 60
},
{
"name": "Bob",
"age": 28,
"active": false,
"status": "approved",
"joinedAt": "2022-12-15",
"score": 80
},
{
"name": "Charlie",
"age": 35,
"active": true,
"status": "rejected",
"joinedAt": "2023-02-10",
"score": 90
},
{
"name": "Dave",
"age": 32,
"active": false,
"status": "approved",
"joinedAt": "2023-03-05",
"score": 70
}
];
76 changes: 76 additions & 0 deletions lib/ui_renderer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
class DynamicTable extends StatelessWidget {
final Map<String, dynamic> schema;
final List<Map<String, dynamic>> data;

const DynamicTable({super.key, required this.schema, required this.data});

@override
Widget build(BuildContext context) {
final columns = schema["columns"] as List;

return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns: columns.map<DataColumn>((col) {
return DataColumn(label: Text(col["field"].toString().toUpperCase()));
}).toList(),
rows: data.map<DataRow>((row) {
return DataRow(
cells: columns.map<DataCell>((col) {
final value = row[col["field"]];
final type = col["type"];
return DataCell(_buildCell(type, value));
}).toList(),
);
}).toList(),
),
);
}

Widget _buildCell(String type, dynamic value) {
switch (type) {
case "toggle":
return Switch(value: value ?? false, onChanged: (_) {});
case "dropdown":
return DropdownButton<String>(
value: value,
items: [value].map((item) {
return DropdownMenuItem<String>(
value: item, // item must be a String
child: Text(item),
);
}).toList(),
onChanged: (_) {},
);
case "chart":
return SizedBox(
width: 100,
height: 100,
child: BarChart(
BarChartData(
titlesData: FlTitlesData(show: false),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(toY: (value as num).toDouble(), width: 20)
])
],
),
),
);
case "date":
return Text(value.toString().split('T').first);
case "slider":
return Slider(
value: (value as num).toDouble(),
onChanged: (_) {},
min: 0,
max: 100,
);
default:
return Text(value.toString());
}
}
}
58 changes: 58 additions & 0 deletions lib/ui_schema_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import json

def analyze_json_to_ui_schema(json_data):
if isinstance(json_data, str):
json_data = json.loads(json_data)

if isinstance(json_data, list) and json_data and isinstance(json_data[0], dict):
fields = json_data[0].keys()
ui_schema = {
"layout": "table",
"columns": []
}

for field in fields:
values = [item.get(field) for item in json_data if field in item]
unique_values = set(values)
ui_type = "text"

if all(isinstance(v, bool) for v in values):
ui_type = "toggle"
elif all(isinstance(v, (int, float)) for v in values):
if len(unique_values) < 10:
ui_type = "dropdown"
else:
ui_type = "chart"
elif len(unique_values) <= 5:
ui_type = "dropdown"

ui_schema["columns"].append({
"field": field,
"type": ui_type,
"sample": values[:3]
})

return ui_schema

elif isinstance(json_data, dict):
return {
"layout": "card",
"fields": [
{"field": k, "type": "text", "value_type": type(v).__name__}
for k, v in json_data.items()
]
}

else:
return {"layout": "text", "message": "Unsupported format"}


if __name__ == "__main__":
mock_json = [
{"name": "Alice", "age": 30, "active": True, "status": "pending"},
{"name": "Bob", "age": 28, "active": False, "status": "approved"},
{"name": "Charlie", "age": 35, "active": True, "status": "pending"}
]

schema = analyze_json_to_ui_schema(mock_json)
print(json.dumps(schema, indent=2))
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
carousel_slider: ^5.0.0
fl_chart: ^0.70.2

dependency_overrides:
extended_text_field: ^16.0.0
Expand Down