You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/copilot/widgets/copilot_message_bubble.dart

242 lines
8.8 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/thinking_orbs/src/thinking_orb.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/sound/sound_player.dart';
import 'package:url_launcher/url_launcher.dart';
import '../model/copilot_message_model.dart';
class CopilotMessageBubble extends StatelessWidget {
final String? userAvatar;
final CopilotMessage message;
final bool showHeader;
const CopilotMessageBubble({Key? key, required this.message, this.showHeader = true, this.userAvatar}) : super(key: key);
@override
Widget build(BuildContext context) {
final isUser = message.role == CopilotRole.user;
return isUser ? _senderCard(context) : _recipientCard(context);
}
String get _timeText => (message.timestamp?.toIso8601String() ?? DateTime.now().toIso8601String()).chatMsgTime;
Widget _senderCard(BuildContext context) {
final senderHeader = Row(
mainAxisSize: MainAxisSize.min,
children: [
const InfoTextLabelWidget(label: "Me"),
8.width,
Container(
height: 36,
width: 36,
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(color: AppColor.background(context), shape: BoxShape.circle, border: Border.all(color: Colors.transparent)),
child: userAvatar != null
? ClipOval(child: Image.network(userAvatar!, width: 14, height: 14))
: Icon(
Icons.person,
size: 16,
color: AppColor.icon2Color(context),
),
),
],
);
return Align(
alignment: Alignment.centerRight,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (showHeader) ...[senderHeader, 4.height] else 8.height,
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
fit: FlexFit.loose,
child: Container(
padding: const EdgeInsets.all(8),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
decoration: BoxDecoration(
color: AppColor.assetGroupBackground(context),
border: Border.all(color: AppColor.borderColor(context)),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (message.localAudioPath != null)
ASoundPlayer(audio: message.localAudioPath!)
else
Text(
message.content,
style: AppTextStyles.bodyText2.copyWith(color: AppColor.textColor(context)),
),
Text(
_timeText,
style: AppTextStyles.bodyText2.copyWith(color: AppColor.textColor(context)),
),
],
),
),
),
(26 + 8).width,
],
),
],
),
);
}
Widget _recipientCard(BuildContext context) {
final showLoadingDots = message.isStreaming && message.content.isEmpty;
final recipientHeader = Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 36,
width: 36,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: AppColor.background(context), shape: BoxShape.circle, border: Border.all(color: Colors.transparent)),
child: "copilot_logo".toSvgAsset(width: 14, height: 14)),
8.width,
const InfoTextLabelWidget(label: "Copilot"),
// Text(
// "Copilot",
// overflow: TextOverflow.ellipsis,
// maxLines: 1,
// style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600),
// ),
],
);
return Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showHeader) ...[recipientHeader, 4.height] else 8.height,
Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.only(left: 26 + 8, right: 26 + 8),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
decoration: BoxDecoration(
color: AppColor.background(context),
border: Border.all(color: AppColor.borderColor(context)),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (showLoadingDots)
Align(
alignment: Alignment.centerLeft,
child: ThinkingOrb(
size: 48,
state: ThinkingOrbState.searching,
theme: context.isDark ? ThinkingOrbTheme.dark : ThinkingOrbTheme.light,
))
else
_buildMessageContent(context),
// if (message.content.isNotEmpty && message.images.isNotEmpty) 8.height,
// if (message.content.isNotEmpty)
// Text(
// message.content,
// style: AppTextStyles.bodyText2.copyWith(
// color: message.isError ? Colors.red : AppColor.white10,
// ),
// ),
if (message.images.isNotEmpty) ...[
8.height,
SizedBox(
height: 90,
width: double.infinity,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: message.images.length,
separatorBuilder: (_, __) => 8.width,
itemBuilder: (context, index) {
final img = message.images[index];
return Image.memory(
base64Decode(img.base64),
width: 100,
height: 100,
fit: BoxFit.contain,
);
},
),
),
],
Align(
alignment: Alignment.centerRight,
widthFactor: 1,
child: Text(
_timeText,
style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
),
),
],
),
),
],
),
);
}
Widget _buildMessageContent(BuildContext context) {
var content = message.content;
content = content.replaceAllMapped(
RegExp(r'(\*\*[^*\n]+:\*\*)'),
(match) => '${match.group(1)} \n',
);
content = content.replaceAllMapped(
RegExp(r'(Link:)\s*', caseSensitive: false),
(match) => '${match.group(1)} \n',
);
return MarkdownBody(
data: content,
selectable: true,
onTapLink: (text, href, title) async {
if (href == null || href.isEmpty) return;
final uri = Uri.tryParse(href);
if (uri == null) return;
await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
},
styleSheet: MarkdownStyleSheet(
p: AppTextStyles.bodyText2.copyWith(
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
),
strong: AppTextStyles.bodyText2.copyWith(
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
fontWeight: FontWeight.bold,
),
listBullet: AppTextStyles.bodyText2.copyWith(
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
),
a: AppTextStyles.bodyText2.copyWith(
color: AppColor.primary10,
decoration: TextDecoration.underline,
),
),
);
}
}