提交 ee9639f1 authored 作者: songchuancai's avatar songchuancai

增加语音播放的按钮

上级 23ffd7b4
...@@ -30,6 +30,8 @@ import 'package:flutter_markdown/flutter_markdown.dart'; ...@@ -30,6 +30,8 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'pages/apps_page.dart'; import 'pages/apps_page.dart';
import 'pages/chat_bubble.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
final String? customTitle; final String? customTitle;
...@@ -266,7 +268,7 @@ class _HomePageState extends State<HomePage> { ...@@ -266,7 +268,7 @@ class _HomePageState extends State<HomePage> {
} }
} }
await systemSpeak(fullResponse); //await systemSpeak(fullResponse);
await _updateCurrentConversation(); await _updateCurrentConversation();
} catch (e) { } catch (e) {
...@@ -323,7 +325,7 @@ class _HomePageState extends State<HomePage> { ...@@ -323,7 +325,7 @@ class _HomePageState extends State<HomePage> {
} }
} }
await systemSpeak(fullResponse); // await systemSpeak(fullResponse);
await _updateCurrentConversation(); await _updateCurrentConversation();
} catch (e) { } catch (e) {
...@@ -791,75 +793,7 @@ class _HomePageState extends State<HomePage> { ...@@ -791,75 +793,7 @@ class _HomePageState extends State<HomePage> {
return Column( return Column(
children: [ children: [
Align( ChatBubble(message: message),
alignment: message.isUserMessage
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.all(12),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.7,
),
decoration: BoxDecoration(
color: message.isUserMessage
? Pallete.firstSuggestionBoxColor
: Pallete.assistantCircleColor,
borderRadius: BorderRadius.circular(15).copyWith(
bottomRight:
message.isUserMessage ? Radius.zero : null,
bottomLeft:
!message.isUserMessage ? Radius.zero : null,
),
),
child: message.isUserMessage
? Text(
message.text,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
)
: MarkdownBody(
data: message.text,
selectable: true,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
color: Colors.black,
fontSize: 16,
),
code: const TextStyle(
color: Colors.white,
fontFamily: 'monospace',
fontSize: 14,
height: 1.5,
backgroundColor: Colors.transparent,
),
codeblockPadding:
const EdgeInsets.all(16),
codeblockDecoration: BoxDecoration(
color: const Color(0xFF1E1E1E),
borderRadius: BorderRadius.circular(8),
),
blockquote: const TextStyle(
color: Colors.black87,
fontSize: 16,
height: 1.5,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: Colors.grey[300]!,
width: 4,
),
),
),
listBullet: const TextStyle(
color: Colors.black87),
),
),
),
),
if (_isLoading && if (_isLoading &&
index == messages.length - 1 && index == messages.length - 1 &&
message.isUserMessage) message.isUserMessage)
......
class ChatMessage { class ChatMessage {
final String text; final String text;
final bool isUserMessage; final bool isUserMessage;
final DateTime timestamp; final DateTime timestamp;
final bool isMarkdown; final bool isMarkdown;
ChatMessage({ ChatMessage({
required this.text, required this.text,
required this.isUserMessage, required this.isUserMessage,
DateTime? timestamp, DateTime? timestamp,
this.isMarkdown = true, this.isMarkdown = true,
}) : timestamp = timestamp ?? DateTime.now(); }) : timestamp = timestamp ?? DateTime.now();
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'text': text, 'text': text,
'isUserMessage': isUserMessage, 'isUserMessage': isUserMessage,
'timestamp': timestamp.toIso8601String(), 'timestamp': timestamp.toIso8601String(),
'isMarkdown': isMarkdown, 'isMarkdown': isMarkdown,
}; };
factory ChatMessage.fromJson(Map<String, dynamic> json) => ChatMessage( factory ChatMessage.fromJson(Map<String, dynamic> json) => ChatMessage(
text: json['text'], text: json['text'],
isUserMessage: json['isUserMessage'], isUserMessage: json['isUserMessage'],
timestamp: DateTime.parse(json['timestamp']), timestamp: DateTime.parse(json['timestamp']),
isMarkdown: json['isMarkdown'], isMarkdown: json['isMarkdown'],
); );
} }
\ No newline at end of file
......
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../models/chat_message.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../pallete.dart';
class ChatBubble extends StatefulWidget {
final ChatMessage message;
const ChatBubble({
Key? key,
required this.message,
}) : super(key: key);
@override
State<ChatBubble> createState() => _ChatBubbleState();
}
class _ChatBubbleState extends State<ChatBubble> {
final FlutterTts flutterTts = FlutterTts();
bool isPlaying = false;
@override
void initState() {
super.initState();
flutterTts.setLanguage("zh-CN");
// 设置语速
flutterTts.setSpeechRate(2.5);
// 设置音调
flutterTts.setPitch(0.8);
flutterTts.setCompletionHandler(() {
if (mounted) {
setState(() {
isPlaying = false;
});
}
});
flutterTts.setCancelHandler(() {
if (mounted) {
setState(() {
isPlaying = false;
});
}
});
flutterTts.setErrorHandler((error) {
if (mounted) {
setState(() {
isPlaying = false;
});
}
});
}
Future<void> _handlePlayStop() async {
if (isPlaying) {
// 如果正在播放,则停止
await flutterTts.stop();
if (mounted) {
setState(() {
isPlaying = false;
});
}
} else {
// 如果未播放,则开始播放
if (mounted) {
setState(() {
isPlaying = true;
});
}
try {
await flutterTts.speak(widget.message.text);
} catch (e) {
print('TTS Error: $e');
if (mounted) {
setState(() {
isPlaying = false;
});
}
}
}
}
@override
Widget build(BuildContext context) {
return Align(
alignment: widget.message.isUserMessage
? Alignment.centerRight
: Alignment.centerLeft,
child: Column(
crossAxisAlignment: widget.message.isUserMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding: const EdgeInsets.all(12),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.7,
),
decoration: BoxDecoration(
color: widget.message.isUserMessage
? Pallete.firstSuggestionBoxColor
: Pallete.assistantCircleColor,
borderRadius: BorderRadius.circular(15).copyWith(
bottomRight: widget.message.isUserMessage ? Radius.zero : null,
bottomLeft: !widget.message.isUserMessage ? Radius.zero : null,
),
),
child: widget.message.isUserMessage
? Text(
widget.message.text,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
)
: MarkdownBody(
data: widget.message.text,
selectable: true,
styleSheet: MarkdownStyleSheet(
p: const TextStyle(
color: Colors.black,
fontSize: 16,
),
code: const TextStyle(
color: Colors.white,
fontFamily: 'monospace',
fontSize: 14,
height: 1.5,
backgroundColor: Colors.transparent,
),
codeblockPadding: const EdgeInsets.all(16),
codeblockDecoration: BoxDecoration(
color: const Color(0xFF1E1E1E),
borderRadius: BorderRadius.circular(8),
),
blockquote: const TextStyle(
color: Colors.black87,
fontSize: 16,
height: 1.5,
),
blockquoteDecoration: BoxDecoration(
border: Border(
left: BorderSide(
color: Colors.grey[300]!,
width: 4,
),
),
),
listBullet: const TextStyle(color: Colors.black87),
),
),
),
if (!widget.message.isUserMessage)
Padding(
padding: const EdgeInsets.only(left: 8.0, top: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
isPlaying ? Icons.stop_circle : Icons.play_circle,
color: Pallete.firstSuggestionBoxColor,
size: 20,
),
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
padding: EdgeInsets.zero,
onPressed: _handlePlayStop,
),
if (isPlaying)
Text(
'正在播放...',
style: TextStyle(
color: Pallete.firstSuggestionBoxColor,
fontSize: 12,
),
),
],
),
),
],
),
);
}
@override
void dispose() {
flutterTts.stop();
super.dispose();
}
}
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
......
...@@ -5,12 +5,16 @@ ...@@ -5,12 +5,16 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import audioplayers_darwin
import flutter_tts import flutter_tts
import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import speech_to_text_macos import speech_to_text_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin"))
} }
...@@ -25,6 +25,62 @@ packages: ...@@ -25,6 +25,62 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.1"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.3"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.2"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.0"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
...@@ -180,10 +236,10 @@ packages: ...@@ -180,10 +236,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.7.1" version: "0.6.7"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
...@@ -264,6 +320,30 @@ packages: ...@@ -264,6 +320,30 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.12"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
...@@ -385,10 +465,10 @@ packages: ...@@ -385,10 +465,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: speech_to_text name: speech_to_text
sha256: "97425fd8cc60424061a0584b6c418c0eedab5201cc5e96ef15a946d7fab7b9b7" sha256: "57fef1d41bdebe298e84842c89bb4ac91f31cdbec7830c8cb1fc6b91d03abd42"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.6.2" version: "6.6.0"
speech_to_text_macos: speech_to_text_macos:
dependency: transitive dependency: transitive
description: description:
...@@ -437,6 +517,14 @@ packages: ...@@ -437,6 +517,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.0+3"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
......
...@@ -43,6 +43,7 @@ dependencies: ...@@ -43,6 +43,7 @@ dependencies:
flutter_markdown: ^0.6.18 flutter_markdown: ^0.6.18
flutter_highlight: ^0.7.0 flutter_highlight: ^0.7.0
markdown: ^7.1.1 markdown: ^7.1.1
audioplayers: ^5.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
...@@ -6,9 +6,12 @@ ...@@ -6,9 +6,12 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <flutter_tts/flutter_tts_plugin.h> #include <flutter_tts/flutter_tts_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
FlutterTtsPluginRegisterWithRegistrar( FlutterTtsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterTtsPlugin")); registry->GetRegistrarForPlugin("FlutterTtsPlugin"));
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
flutter_tts flutter_tts
) )
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论