Skip to content
Merged
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
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