提交 8631150a authored 作者: songchuancai's avatar songchuancai

优化语音功能

上级 104d31dd
......@@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
throw new FileNotFoundException ("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
......
......@@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<queries>
<intent>
<action android:name="android.speech.RecognitionService" />
......
import 'package:allen/pallete.dart';
import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:uuid/uuid.dart';
import 'models/conversation.dart';
import 'services/storage_service.dart';
import 'services/chat_service.dart';
import 'models/chat_message.dart';
import 'models/user.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'pages/apps_page.dart';
import 'pages/chat_bubble.dart';
class HomePage extends StatefulWidget {
final String? customTitle;
final String? customDescription;
final String? customImageUrl;
final bool hideNavigation;
const HomePage({
super.key,
this.customTitle,
this.customDescription,
this.customImageUrl,
this.hideNavigation = false,
});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final speechToText = SpeechToText();
final flutterTts = FlutterTts();
String lastWords = '';
final OpenAIService openAIService = OpenAIService();
String? generatedContent;
String? generatedImageUrl;
int start = 200;
int delay = 200;
final TextEditingController _messageController = TextEditingController();
String currentStreamedContent = '';
List<ChatMessage> messages = [];
bool _isLoading = false;
bool _isVoiceMode = true;
bool _isListeningPressed = false;
String _currentVoiceText = '';
late StorageService _storageService;
late SharedPreferences _prefs;
late Conversation _currentConversation;
List<Conversation> _conversations = [];
int _currentIndex = 0;
@override
void initState() {
super.initState();
_initializeStorage();
initSpeechToText();
_initTts();
}
Future<void> _initializeStorage() async {
_prefs = await SharedPreferences.getInstance();
_storageService = StorageService(_prefs);
_conversations = await _storageService.getConversations();
// 每次进入应用创建一个新的会话
_createNewConversation();
// if (_conversations.isEmpty) {
// _createNewConversation();
// } else {
// _currentConversation = _conversations.first;
// setState(() {
// messages = _currentConversation.messages;
// });
// }
}
Future<void> _createNewConversation() async {
final newConversation = Conversation(
id: const Uuid().v4(),
title: '新会话 ${_conversations.length + 1}',
createdAt: DateTime.now(),
messages: [],
);
await _storageService.addConversation(newConversation);
setState(() {
_conversations.insert(0, newConversation);
_currentConversation = newConversation;
messages = [];
});
}
Future<void> _updateCurrentConversation() async {
_currentConversation = Conversation(
id: _currentConversation.id,
title: _currentConversation.title,
createdAt: _currentConversation.createdAt,
messages: messages,
);
await _storageService.updateConversation(_currentConversation);
}
Future<void> _initTts() async {
if (!kIsWeb) {
await flutterTts.setSharedInstance(true);
}
setState(() {});
}
Future<void> initSpeechToText() async {
await speechToText.initialize();
setState(() {});
}
Future<void> startListening() async {
await speechToText.listen(onResult: onSpeechResult);
setState(() {});
}
Future<void> stopListening() async {
await speechToText.stop();
setState(() {});
}
Future<void> onSpeechResult(SpeechRecognitionResult result) async {
setState(() {
lastWords = result.recognizedWords;
_currentVoiceText = result.recognizedWords;
});
}
Future<void> systemSpeak(String content) async {
try {
if (kIsWeb) {
// 设置语速
await flutterTts.setSpeechRate(1);
// 音调
await flutterTts.setPitch(0.8);
await flutterTts.speak(content);
} else {
await flutterTts.setSharedInstance(true);
await flutterTts.speak(content);
}
} catch (e) {
print('TTS Error: $e');
}
}
Future<void> _sendMessage() async {
if (!mounted) return; // 添加这行
if (_messageController.text.isEmpty) return;
String userMessage = _messageController.text;
_messageController.clear();
setState(() {
messages.add(ChatMessage(
text: userMessage,
isUserMessage: true,
));
currentStreamedContent = '';
_isLoading = true;
});
try {
String fullResponse = '';
bool isFirstChunk = true;
// 创建一个缓冲区来存储收到的文本
StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userMessage)) {
buffer.write(chunk);
// 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() {
fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) {
messages.add(ChatMessage(
text: fullResponse,
isUserMessage: false,
));
isFirstChunk = false;
} else {
messages.last = ChatMessage(
text: fullResponse,
isUserMessage: false,
);
}
});
// 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50));
}
}
//await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) {
setState(() {
messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e',
isUserMessage: false,
));
});
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _processAIResponse(String userInput) async {
try {
String fullResponse = '';
bool isFirstChunk = true;
// 创建一个缓冲区来储收到的文本
StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userInput)) {
buffer.write(chunk);
// 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() {
fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) {
messages.add(ChatMessage(
text: fullResponse,
isUserMessage: false,
));
isFirstChunk = false;
} else {
messages.last = ChatMessage(
text: fullResponse,
isUserMessage: false,
);
}
});
// 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50));
}
}
// await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) {
setState(() {
messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e',
isUserMessage: false,
));
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
void dispose() {
super.dispose();
speechToText.stop();
flutterTts.stop();
_messageController.dispose();
}
Widget _buildBottomInput() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, -1),
),
],
),
child: Row(
children: [
Expanded(
child: _isVoiceMode
? GestureDetector(
onLongPressStart: (_) async {
setState(() {
_isListeningPressed = true;
_currentVoiceText = '';
});
await startListening();
},
onLongPressEnd: (_) async {
setState(() => _isListeningPressed = false);
await stopListening();
final finalVoiceText = _currentVoiceText;
if (finalVoiceText.isNotEmpty) {
setState(() {
messages.add(ChatMessage(
text: finalVoiceText,
isUserMessage: true,
));
_isLoading = true;
});
await _processAIResponse(finalVoiceText);
}
setState(() {
_currentVoiceText = '';
});
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(25),
),
child: Text(
_isListeningPressed
? (_currentVoiceText.isEmpty
? '正在聆听...'
: _currentVoiceText)
: '按住说话',
textAlign: TextAlign.center,
style: TextStyle(
color: _isListeningPressed
? Pallete.firstSuggestionBoxColor
: Colors.grey[600],
),
),
),
)
: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: '输入消息...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
),
),
),
const SizedBox(width: 8),
IconButton(
icon: Icon(
_isVoiceMode ? Icons.keyboard : Icons.mic,
color: Pallete.firstSuggestionBoxColor,
),
onPressed: () {
setState(() => _isVoiceMode = !_isVoiceMode);
},
),
if (!_isVoiceMode)
IconButton(
icon: const Icon(
Icons.send,
color: Pallete.firstSuggestionBoxColor,
),
onPressed: _sendMessage,
),
],
),
);
}
AppBar _buildAppBar() {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: () {
setState(() => _currentIndex = 0);
},
child: Text(
'对话',
style: TextStyle(
fontSize: _currentIndex == 0 ? 20 : 16,
fontWeight:
_currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 0
? Pallete.firstSuggestionBoxColor
: Colors.grey,
),
),
),
const SizedBox(width: 20),
TextButton(
onPressed: () {
setState(() => _currentIndex = 1);
},
child: Text(
'应用',
style: TextStyle(
fontSize: _currentIndex == 1 ? 20 : 16,
fontWeight:
_currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 1
? Pallete.firstSuggestionBoxColor
: Colors.grey,
),
),
),
],
),
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
);
}
Widget _buildDrawer(BuildContext context) {
return Drawer(
child: Column(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor,
),
child: const Center(
child: Text(
'会话列表',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
ListTile(
leading: const Icon(Icons.add),
title: const Text('新建会话'),
onTap: () {
_createNewConversation();
Navigator.pop(context);
},
),
Expanded(
child: ListView.builder(
itemCount: _conversations.length,
itemBuilder: (context, index) {
final conversation = _conversations[index];
return ListTile(
leading: const Icon(Icons.chat),
title: Text(conversation.title),
subtitle: Text(
conversation.messages.isEmpty
? '暂无消息'
: conversation.messages.last.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
selected: _currentConversation.id == conversation.id,
onTap: () {
setState(() {
_currentConversation = conversation;
messages = conversation.messages;
});
Navigator.pop(context);
},
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await _storageService.deleteConversation(conversation.id);
setState(() {
_conversations.removeAt(index);
if (_currentConversation.id == conversation.id) {
if (_conversations.isEmpty) {
_createNewConversation();
} else {
_currentConversation = _conversations.first;
messages = _currentConversation.messages;
}
}
});
},
),
);
},
),
),
const Spacer(),
const Divider(height: 1),
FutureBuilder<User?>(
future: StorageService.getUser(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[50],
),
child: Column(
children: [
Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor,
shape: BoxShape.circle,
),
child: Center(
child: Text(
snapshot.data!.username[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
snapshot.data!.username,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'在线',
style: TextStyle(
fontSize: 14,
color: Colors.green[600],
),
),
],
),
),
],
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认退出'),
content: const Text('您确定要退出登录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text(
'退出',
style: TextStyle(color: Colors.red),
),
),
],
),
);
if (confirmed == true && context.mounted) {
await StorageService.clearUser();
Navigator.of(context).pushReplacementNamed('/login');
}
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.logout,
color: Colors.red[700],
size: 20,
),
const SizedBox(width: 8),
Text(
'退出登录',
style: TextStyle(
color: Colors.red[700],
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
),
);
},
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.hideNavigation ? null : _buildAppBar(),
drawer: widget.hideNavigation ? null : _buildDrawer(context),
body: IndexedStack(
index: _currentIndex,
children: [
Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: messages.isEmpty ? 1 : messages.length,
itemBuilder: (context, index) {
if (messages.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ZoomIn(
child: Stack(
children: [
Center(
child: Container(
height: 120,
width: 120,
margin: const EdgeInsets.only(top: 4),
decoration: const BoxDecoration(
color: Pallete.assistantCircleColor,
shape: BoxShape.circle,
),
),
),
Container(
height: 123,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: widget.customImageUrl != null
? NetworkImage(
widget.customImageUrl!)
: const AssetImage(
'assets/images/virtualAssistant.png',
) as ImageProvider,
onError: (exception, stackTrace) {
print(
'Error loading image: $exception');
},
),
),
),
],
),
),
const SizedBox(height: 20),
Text(
widget.customDescription ??
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?',
style: const TextStyle(
fontSize: 20,
color: Pallete.mainFontColor,
),
textAlign: TextAlign.center,
),
],
),
);
}
final message = messages[index];
return Column(
children: [
ChatBubble(
message: message,
isTyping: _isLoading &&
index == messages.length - 1 &&
!message.isUserMessage,
),
if (_isLoading &&
index == messages.length - 1 &&
message.isUserMessage)
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Pallete.firstSuggestionBoxColor,
),
),
),
const SizedBox(width: 8),
const Text(
'正在思考中...',
style: TextStyle(
color: Pallete.mainFontColor,
fontSize: 14,
),
),
],
),
),
),
],
);
},
),
),
_buildBottomInput(),
],
),
if (!widget.hideNavigation) AppsPage(),
],
),
);
}
}
import 'package:allen/pallete.dart';
import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:uuid/uuid.dart';
import 'models/conversation.dart';
import 'services/storage_service.dart';
import 'services/chat_service.dart';
import 'models/chat_message.dart';
import 'models/user.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'pages/apps_page.dart';
import 'pages/chat_bubble.dart';
class HomePage extends StatefulWidget {
final String? customTitle;
final String? customDescription;
final String? customImageUrl;
final bool hideNavigation;
const HomePage({
super.key,
this.customTitle,
this.customDescription,
this.customImageUrl,
this.hideNavigation = false,
});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final speechToText = SpeechToText();
final flutterTts = FlutterTts();
String lastWords = '';
final OpenAIService openAIService = OpenAIService();
String? generatedContent;
String? generatedImageUrl;
int start = 200;
int delay = 200;
final TextEditingController _messageController = TextEditingController();
String currentStreamedContent = '';
List<ChatMessage> messages = [];
bool _isLoading = false;
bool _isVoiceMode = true;
bool _isListeningPressed = false;
String _currentVoiceText = '';
late StorageService _storageService;
late SharedPreferences _prefs;
late Conversation _currentConversation;
List<Conversation> _conversations = [];
int _currentIndex = 0;
bool _hasSpeechPermission = false;
@override
void initState() {
super.initState();
_initializeStorage();
initSpeechToText();
_initTts();
}
Future<void> _initializeStorage() async {
_prefs = await SharedPreferences.getInstance();
_storageService = StorageService(_prefs);
_conversations = await _storageService.getConversations();
// 每次进入应用创建一个新的会话
_createNewConversation();
// if (_conversations.isEmpty) {
// _createNewConversation();
// } else {
// _currentConversation = _conversations.first;
// setState(() {
// messages = _currentConversation.messages;
// });
// }
}
Future<void> _createNewConversation() async {
final newConversation = Conversation(
id: const Uuid().v4(),
title: '新会话 ${_conversations.length + 1}',
createdAt: DateTime.now(),
messages: [],
);
await _storageService.addConversation(newConversation);
setState(() {
_conversations.insert(0, newConversation);
_currentConversation = newConversation;
messages = [];
});
}
Future<void> _updateCurrentConversation() async {
_currentConversation = Conversation(
id: _currentConversation.id,
title: _currentConversation.title,
createdAt: _currentConversation.createdAt,
messages: messages,
);
await _storageService.updateConversation(_currentConversation);
}
Future<void> _initTts() async {
if (!kIsWeb) {
await flutterTts.setSharedInstance(true);
}
setState(() {});
}
Future<void> initSpeechToText() async {
try {
_hasSpeechPermission = await speechToText.initialize(
onStatus: (status) {
print('语音状态: $status');
if (status == 'notListening') {
setState(() => _isListeningPressed = false);
}
},
onError: (error) {
print('语音错误: $error');
setState(() => _isListeningPressed = false);
_showErrorDialog('语音初始化失败,请检查麦克风权限');
},
);
} catch (e) {
print('语音初始化错误: $e');
_hasSpeechPermission = false;
}
setState(() {});
}
Future<void> startListening() async {
if (!_hasSpeechPermission) {
_showErrorDialog('请先授予麦克风权限');
setState(() => _isListeningPressed = false);
return;
}
try {
await speechToText.listen(
onResult: onSpeechResult,
localeId: 'zh_CN',
listenMode: ListenMode.confirmation,
cancelOnError: true,
partialResults: true,
);
} catch (e) {
print('开始录音失败: $e');
setState(() => _isListeningPressed = false);
_showErrorDialog('启动语音识别失败,请重试');
}
}
Future<void> stopListening() async {
await speechToText.stop();
setState(() {});
}
Future<void> onSpeechResult(SpeechRecognitionResult result) async {
setState(() {
lastWords = result.recognizedWords;
_currentVoiceText = result.recognizedWords;
});
}
Future<void> systemSpeak(String content) async {
try {
if (kIsWeb) {
// 设置语速
await flutterTts.setSpeechRate(1);
// 音调
await flutterTts.setPitch(0.8);
await flutterTts.speak(content);
} else {
await flutterTts.setSharedInstance(true);
await flutterTts.speak(content);
}
} catch (e) {
print('TTS Error: $e');
}
}
Future<void> _sendMessage() async {
if (!mounted) return; // 添加这行
if (_messageController.text.isEmpty) return;
String userMessage = _messageController.text;
_messageController.clear();
setState(() {
messages.add(ChatMessage(
text: userMessage,
isUserMessage: true,
));
currentStreamedContent = '';
_isLoading = true;
});
try {
String fullResponse = '';
bool isFirstChunk = true;
// 创建一个缓冲区来存储收到的文本
StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userMessage)) {
buffer.write(chunk);
// 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() {
fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) {
messages.add(ChatMessage(
text: fullResponse,
isUserMessage: false,
));
isFirstChunk = false;
} else {
messages.last = ChatMessage(
text: fullResponse,
isUserMessage: false,
);
}
});
// 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50));
}
}
//await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) {
setState(() {
messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e',
isUserMessage: false,
));
});
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _processAIResponse(String userInput) async {
try {
String fullResponse = '';
bool isFirstChunk = true;
// 创建一个缓冲区来储收到的文本
StringBuffer buffer = StringBuffer();
await for (final chunk in openAIService.chatGPTAPI(userInput)) {
buffer.write(chunk);
// 逐字显示文本
for (int i = fullResponse.length; i < buffer.length; i++) {
setState(() {
fullResponse += buffer.toString()[i];
if (isFirstChunk && i == 0) {
messages.add(ChatMessage(
text: fullResponse,
isUserMessage: false,
));
isFirstChunk = false;
} else {
messages.last = ChatMessage(
text: fullResponse,
isUserMessage: false,
);
}
});
// 添加短暂延迟以创建打字效果
await Future.delayed(const Duration(milliseconds: 50));
}
}
// await systemSpeak(fullResponse);
await _updateCurrentConversation();
} catch (e) {
setState(() {
messages.add(ChatMessage(
text: '抱歉,出现了一些错误:$e',
isUserMessage: false,
));
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
void dispose() {
super.dispose();
speechToText.stop();
flutterTts.stop();
_messageController.dispose();
}
Widget _buildBottomInput() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, -1),
),
],
),
child: Row(
children: [
Expanded(
child: _isVoiceMode
? GestureDetector(
onTap: () async {
if (!_isListeningPressed) {
// 开始录音
setState(() {
_isListeningPressed = true;
_currentVoiceText = '';
});
await startListening();
} else {
// 停止录音
setState(() => _isListeningPressed = false);
await stopListening();
// 确保有语音文本才处理
if (_currentVoiceText.isNotEmpty) {
setState(() {
messages.add(ChatMessage(
text: _currentVoiceText,
isUserMessage: true,
));
_isLoading = true;
});
await _processAIResponse(_currentVoiceText);
}
setState(() => _currentVoiceText = '');
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(25),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_isListeningPressed ? Icons.stop : Icons.mic,
color: _isListeningPressed
? Pallete.firstSuggestionBoxColor
: Colors.grey[600],
),
const SizedBox(width: 8),
Text(
_isListeningPressed
? (_currentVoiceText.isEmpty ? '点击停止' : _currentVoiceText)
: '点击说话',
textAlign: TextAlign.center,
style: TextStyle(
color: _isListeningPressed
? Pallete.firstSuggestionBoxColor
: Colors.grey[600],
),
),
],
),
),
)
: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: '输入消息...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
),
),
),
const SizedBox(width: 8),
IconButton(
icon: Icon(
_isVoiceMode ? Icons.keyboard : Icons.mic,
color: Pallete.firstSuggestionBoxColor,
),
onPressed: () {
setState(() => _isVoiceMode = !_isVoiceMode);
},
),
if (!_isVoiceMode)
IconButton(
icon: const Icon(
Icons.send,
color: Pallete.firstSuggestionBoxColor,
),
onPressed: _sendMessage,
),
],
),
);
}
AppBar _buildAppBar() {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: () {
setState(() => _currentIndex = 0);
},
child: Text(
'对话',
style: TextStyle(
fontSize: _currentIndex == 0 ? 20 : 16,
fontWeight:
_currentIndex == 0 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 0
? Pallete.firstSuggestionBoxColor
: Colors.grey,
),
),
),
const SizedBox(width: 20),
TextButton(
onPressed: () {
setState(() => _currentIndex = 1);
},
child: Text(
'应用',
style: TextStyle(
fontSize: _currentIndex == 1 ? 20 : 16,
fontWeight:
_currentIndex == 1 ? FontWeight.bold : FontWeight.normal,
color: _currentIndex == 1
? Pallete.firstSuggestionBoxColor
: Colors.grey,
),
),
),
],
),
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
);
}
Widget _buildDrawer(BuildContext context) {
return Drawer(
child: Column(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor,
),
child: const Center(
child: Text(
'会话列表',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
),
ListTile(
leading: const Icon(Icons.add),
title: const Text('新建会话'),
onTap: () {
_createNewConversation();
Navigator.pop(context);
},
),
Expanded(
child: ListView.builder(
itemCount: _conversations.length,
itemBuilder: (context, index) {
final conversation = _conversations[index];
return ListTile(
leading: const Icon(Icons.chat),
title: Text(conversation.title),
subtitle: Text(
conversation.messages.isEmpty
? '暂无消息'
: conversation.messages.last.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
selected: _currentConversation.id == conversation.id,
onTap: () {
setState(() {
_currentConversation = conversation;
messages = conversation.messages;
});
Navigator.pop(context);
},
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await _storageService.deleteConversation(conversation.id);
setState(() {
_conversations.removeAt(index);
if (_currentConversation.id == conversation.id) {
if (_conversations.isEmpty) {
_createNewConversation();
} else {
_currentConversation = _conversations.first;
messages = _currentConversation.messages;
}
}
});
},
),
);
},
),
),
const Spacer(),
const Divider(height: 1),
FutureBuilder<User?>(
future: StorageService.getUser(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const SizedBox();
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[50],
),
child: Column(
children: [
Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Pallete.firstSuggestionBoxColor,
shape: BoxShape.circle,
),
child: Center(
child: Text(
snapshot.data!.username[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
snapshot.data!.username,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'在线',
style: TextStyle(
fontSize: 14,
color: Colors.green[600],
),
),
],
),
),
],
),
const SizedBox(height: 16),
InkWell(
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认退出'),
content: const Text('您确定要退出登录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text(
'退出',
style: TextStyle(color: Colors.red),
),
),
],
),
);
if (confirmed == true && context.mounted) {
await StorageService.clearUser();
Navigator.of(context).pushReplacementNamed('/login');
}
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.logout,
color: Colors.red[700],
size: 20,
),
const SizedBox(width: 8),
Text(
'出登录',
style: TextStyle(
color: Colors.red[700],
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
),
);
},
),
],
),
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('提示'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.hideNavigation ? null : _buildAppBar(),
drawer: widget.hideNavigation ? null : _buildDrawer(context),
body: IndexedStack(
index: _currentIndex,
children: [
Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: messages.isEmpty ? 1 : messages.length,
itemBuilder: (context, index) {
if (messages.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ZoomIn(
child: Stack(
children: [
Center(
child: Container(
height: 120,
width: 120,
margin: const EdgeInsets.only(top: 4),
decoration: const BoxDecoration(
color: Pallete.assistantCircleColor,
shape: BoxShape.circle,
),
),
),
Container(
height: 123,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: widget.customImageUrl != null
? NetworkImage(
widget.customImageUrl!)
: const AssetImage(
'assets/images/virtualAssistant.png',
) as ImageProvider,
onError: (exception, stackTrace) {
print(
'Error loading image: $exception');
},
),
),
),
],
),
),
const SizedBox(height: 20),
Text(
widget.customDescription ??
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?',
style: const TextStyle(
fontSize: 20,
color: Pallete.mainFontColor,
),
textAlign: TextAlign.center,
),
],
),
);
}
final message = messages[index];
return Column(
children: [
ChatBubble(
message: message,
isTyping: _isLoading &&
index == messages.length - 1 &&
!message.isUserMessage,
),
if (_isLoading &&
index == messages.length - 1 &&
message.isUserMessage)
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Pallete.firstSuggestionBoxColor,
),
),
),
const SizedBox(width: 8),
const Text(
'正在思考中...',
style: TextStyle(
color: Pallete.mainFontColor,
fontSize: 14,
),
),
],
),
),
),
],
);
},
),
),
_buildBottomInput(),
],
),
if (!widget.hideNavigation) AppsPage(),
],
),
);
}
}
\ No newline at end of file
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论