Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Require Dart 3.5 or later.
* Remove dependency on `package:gap`.
* fixed #40: Unrecoverable Black screen
* implements #37: Custom Callback on Prompt/Response Error

## 0.6.7

Expand Down
48 changes: 48 additions & 0 deletions example/lib/callbacks/on_cancel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2024 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
import 'package:google_generative_ai/google_generative_ai.dart';

import '../gemini_api_key.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
static const title = 'Example: Welcome Message';

const App({super.key});

@override
Widget build(BuildContext context) => const MaterialApp(
title: title,
home: ChatPage(),
);
}

class ChatPage extends StatelessWidget {
const ChatPage({super.key});

void _onCancel(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Chat cancelled')),
);
}

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: LlmChatView(
onCancelCallback: _onCancel,
cancelMessage: 'Request cancelled',
provider: GeminiProvider(
model: GenerativeModel(
model: 'gemini-1.5-flash',
apiKey: geminiApiKey,
),
),
),
);
}
48 changes: 48 additions & 0 deletions example/lib/callbacks/on_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2024 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
import 'package:google_generative_ai/google_generative_ai.dart';

import '../gemini_api_key.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
static const title = 'Example: Welcome Message';

const App({super.key});

@override
Widget build(BuildContext context) => const MaterialApp(
title: title,
home: ChatPage(),
);
}

class ChatPage extends StatelessWidget {
const ChatPage({super.key});

void _onError(BuildContext context, LlmException error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${error.message}')),
);
}

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: LlmChatView(
onErrorCallback: _onError,
errorMessage: 'An error occurred',
provider: GeminiProvider(
model: GenerativeModel(
model: 'gemini-1.5-flash',
apiKey: geminiApiKey,
),
),
),
);
}
62 changes: 55 additions & 7 deletions lib/src/views/llm_chat_view/llm_chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,27 @@ class LlmChatView extends StatefulWidget {
/// when the chat history is empty. Defaults to an empty list.
/// - [welcomeMessage]: Optional. A welcome message to display when the chat
/// is first opened.
/// - [onCancelCallback]: Optional. The action to perform when the user
/// cancels a chat operation. By default, a snackbar is displayed with the
/// canceled message.
/// - [onErrorCallback]: Optional. The action to perform when an
/// error occurs during a chat operation. By default, an alert dialog is
/// displayed with the error message.
/// - [cancelMessage]: Optional. The message to display when the user cancels
/// a chat operation. Defaults to 'CANCEL'.
/// - [errorMessage]: Optional. The message to display when an error occurs
/// during a chat operation. Defaults to 'ERROR'.
LlmChatView({
required LlmProvider provider,
LlmChatViewStyle? style,
ResponseBuilder? responseBuilder,
LlmStreamGenerator? messageSender,
this.suggestions = const [],
String? welcomeMessage,
this.onCancelCallback,
this.onErrorCallback,
this.cancelMessage = 'CANCEL',
this.errorMessage = 'ERROR',
super.key,
}) : viewModel = ChatViewModel(
provider: provider,
Expand All @@ -95,6 +109,27 @@ class LlmChatView extends StatefulWidget {
/// It encapsulates the core data and functionality needed for the chat view.
late final ChatViewModel viewModel;

/// The action to perform when the user cancels a chat operation.
///
/// By default, a snackbar is displayed with the canceled message.
final void Function(BuildContext context)? onCancelCallback;

/// The action to perform when an error occurs during a chat operation.
///
/// By default, an alert dialog is displayed with the error message.
final void Function(BuildContext context, LlmException error)?
onErrorCallback;

/// The text message to display when the user cancels a chat operation.
///
/// Defaults to 'CANCEL'.
final String cancelMessage;

/// The text message to display when an error occurs during a chat operation.
///
/// Defaults to 'ERROR'.
final String errorMessage;

@override
State<LlmChatView> createState() => _LlmChatViewState();
}
Expand Down Expand Up @@ -280,19 +315,32 @@ class _LlmChatViewState extends State<LlmChatView>
// empty LLM message.
final llmMessage = widget.viewModel.provider.history.last;
if (llmMessage.text == null) {
llmMessage.append(error is LlmCancelException ? 'CANCEL' : 'ERROR');
llmMessage.append(
error is LlmCancelException
? widget.cancelMessage
: widget.errorMessage,
);
}

switch (error) {
case LlmCancelException():
AdaptiveSnackBar.show(context, 'LLM operation canceled by user');
if (widget.onCancelCallback != null) {
widget.onCancelCallback!(context);
} else {
AdaptiveSnackBar.show(context, 'LLM operation canceled by user');
}
break;
case LlmFailureException():
case LlmException():
await AdaptiveAlertDialog.show(
context: context,
content: Text(error.toString()),
showOK: true,
);
if (widget.onErrorCallback != null) {
widget.onErrorCallback!(context, error);
} else {
await AdaptiveAlertDialog.show(
context: context,
content: Text(error.toString()),
showOK: true,
);
}
}
}

Expand Down
Loading