verso_connect 0.1.3
verso_connect: ^0.1.3 copied to clipboard
Verso Connect for Flutter: let users connect their ChatGPT account from inside your app.
example/lib/main.dart
// A stand-in for a partner app: one screen, one button. Tapping it asks the
// partner backend for a connect link, then hands that link to VersoConnect.
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:verso_connect/verso_connect.dart';
import 'demo_config.dart';
// What a real app derives from its own logged-in user.
final String userRef = demoUserRef.isNotEmpty
? demoUserRef
: 'demo-${Random().nextInt(1 << 32).toRadixString(16).padLeft(8, '0')}';
/// The one call a partner app makes to its own backend before showing the flow.
Future<Uri> fetchConnectLink(String userRef) async {
final res = await http.post(
Uri.parse('$demoBackendUrl/connect-link'),
headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer $demoKey'},
body: jsonEncode({'userRef': userRef}),
);
if (res.statusCode != 200) throw Exception('backend answered ${res.statusCode}');
return Uri.parse((jsonDecode(res.body) as Map<String, dynamic>)['url'] as String);
}
void main() => runApp(const PartnerApp());
class PartnerApp extends StatelessWidget {
const PartnerApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Partner app',
theme: ThemeData(colorSchemeSeed: const Color(0xFF0A7CFF), useMaterial3: true),
home: const HomeScreen(),
);
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool busy = false;
String? connectionId;
String message = '';
Future<void> connect() async {
setState(() {
busy = true;
message = 'Asking the backend for a connect link…';
});
try {
final link = await fetchConnectLink(userRef);
setState(() => message = 'Opening ChatGPT…');
final connection = await VersoConnect.present(link);
setState(() {
connectionId = connection.connectionId;
message = '';
});
} on VersoConnectException catch (e) {
setState(() => message = e.isCancelled ? 'Cancelled.' : 'Failed: ${e.message}');
} catch (e) {
setState(() => message = 'Failed: $e');
} finally {
setState(() => busy = false);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Partner app', style: theme.textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
Text(
'Connect your ChatGPT account to bring your conversations into this app.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(color: Colors.grey[700]),
),
const SizedBox(height: 24),
if (connectionId != null) ...[
const Text('ChatGPT connected',
style: TextStyle(color: Color(0xFF1A7F37), fontWeight: FontWeight.w600, fontSize: 17)),
Text('connection $connectionId',
style: const TextStyle(fontFamily: 'monospace', fontSize: 12, color: Colors.grey)),
const SizedBox(height: 16),
],
FilledButton(
onPressed: busy ? null : connect,
child: Text(busy ? 'Connecting…' : (connectionId == null ? 'Connect ChatGPT' : 'Reconnect')),
),
if (message.isNotEmpty) ...[
const SizedBox(height: 16),
Text(message, textAlign: TextAlign.center, style: theme.textTheme.bodySmall),
],
],
),
),
Text('user $userRef', style: const TextStyle(fontFamily: 'monospace', fontSize: 10, color: Colors.grey)),
],
),
),
),
);
}
}