Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
F
flutter-chat
Project
Project
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
Graph
比较
统计图
议题
0
议题
0
列表
看板
标记
Milestones
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
songchuancai
flutter-chat
Commits
8631150a
提交
8631150a
authored
11月 11, 2024
作者:
songchuancai
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
优化语音功能
上级
104d31dd
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
911 行增加
和
853 行删除
+911
-853
build.gradle
android/app/build.gradle
+1
-1
AndroidManifest.xml
android/app/src/main/AndroidManifest.xml
+2
-0
home_page.dart
lib/home_page.dart
+908
-852
没有找到文件。
android/app/build.gradle
浏览文件 @
8631150a
...
@@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
...
@@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def
flutterRoot
=
localProperties
.
getProperty
(
'flutter.sdk'
)
def
flutterRoot
=
localProperties
.
getProperty
(
'flutter.sdk'
)
if
(
flutterRoot
==
null
)
{
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'
)
def
flutterVersionCode
=
localProperties
.
getProperty
(
'flutter.versionCode'
)
...
...
android/app/src/main/AndroidManifest.xml
浏览文件 @
8631150a
...
@@ -5,6 +5,8 @@
...
@@ -5,6 +5,8 @@
<uses-permission
android:name=
"android.permission.BLUETOOTH"
/>
<uses-permission
android:name=
"android.permission.BLUETOOTH"
/>
<uses-permission
android:name=
"android.permission.BLUETOOTH_ADMIN"
/>
<uses-permission
android:name=
"android.permission.BLUETOOTH_ADMIN"
/>
<uses-permission
android:name=
"android.permission.BLUETOOTH_CONNECT"
/>
<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>
<queries>
<intent>
<intent>
<action
android:name=
"android.speech.RecognitionService"
/>
<action
android:name=
"android.speech.RecognitionService"
/>
...
...
lib/home_page.dart
浏览文件 @
8631150a
import
'package:allen/pallete.dart'
;
import
'package:allen/pallete.dart'
;
import
'package:animate_do/animate_do.dart'
;
import
'package:animate_do/animate_do.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_tts/flutter_tts.dart'
;
import
'package:flutter_tts/flutter_tts.dart'
;
import
'package:speech_to_text/speech_recognition_result.dart'
;
import
'package:speech_to_text/speech_recognition_result.dart'
;
import
'package:speech_to_text/speech_to_text.dart'
;
import
'package:speech_to_text/speech_to_text.dart'
;
import
'package:flutter/foundation.dart'
show
kIsWeb
;
import
'package:flutter/foundation.dart'
show
kIsWeb
;
import
'package:uuid/uuid.dart'
;
import
'package:uuid/uuid.dart'
;
import
'models/conversation.dart'
;
import
'models/conversation.dart'
;
import
'services/storage_service.dart'
;
import
'services/storage_service.dart'
;
import
'services/chat_service.dart'
;
import
'services/chat_service.dart'
;
import
'models/chat_message.dart'
;
import
'models/chat_message.dart'
;
import
'models/user.dart'
;
import
'models/user.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'pages/apps_page.dart'
;
import
'pages/apps_page.dart'
;
import
'pages/chat_bubble.dart'
;
import
'pages/chat_bubble.dart'
;
class
HomePage
extends
StatefulWidget
{
class
HomePage
extends
StatefulWidget
{
final
String
?
customTitle
;
final
String
?
customTitle
;
final
String
?
customDescription
;
final
String
?
customDescription
;
final
String
?
customImageUrl
;
final
String
?
customImageUrl
;
final
bool
hideNavigation
;
final
bool
hideNavigation
;
const
HomePage
({
const
HomePage
({
super
.
key
,
super
.
key
,
this
.
customTitle
,
this
.
customTitle
,
this
.
customDescription
,
this
.
customDescription
,
this
.
customImageUrl
,
this
.
customImageUrl
,
this
.
hideNavigation
=
false
,
this
.
hideNavigation
=
false
,
});
});
@override
@override
State
<
HomePage
>
createState
()
=>
_HomePageState
();
State
<
HomePage
>
createState
()
=>
_HomePageState
();
}
}
class
_HomePageState
extends
State
<
HomePage
>
{
class
_HomePageState
extends
State
<
HomePage
>
{
final
speechToText
=
SpeechToText
();
final
speechToText
=
SpeechToText
();
final
flutterTts
=
FlutterTts
();
final
flutterTts
=
FlutterTts
();
String
lastWords
=
''
;
String
lastWords
=
''
;
final
OpenAIService
openAIService
=
OpenAIService
();
final
OpenAIService
openAIService
=
OpenAIService
();
String
?
generatedContent
;
String
?
generatedContent
;
String
?
generatedImageUrl
;
String
?
generatedImageUrl
;
int
start
=
200
;
int
start
=
200
;
int
delay
=
200
;
int
delay
=
200
;
final
TextEditingController
_messageController
=
TextEditingController
();
final
TextEditingController
_messageController
=
TextEditingController
();
String
currentStreamedContent
=
''
;
String
currentStreamedContent
=
''
;
List
<
ChatMessage
>
messages
=
[];
List
<
ChatMessage
>
messages
=
[];
bool
_isLoading
=
false
;
bool
_isLoading
=
false
;
bool
_isVoiceMode
=
true
;
bool
_isVoiceMode
=
true
;
bool
_isListeningPressed
=
false
;
bool
_isListeningPressed
=
false
;
String
_currentVoiceText
=
''
;
String
_currentVoiceText
=
''
;
late
StorageService
_storageService
;
late
StorageService
_storageService
;
late
SharedPreferences
_prefs
;
late
SharedPreferences
_prefs
;
late
Conversation
_currentConversation
;
late
Conversation
_currentConversation
;
List
<
Conversation
>
_conversations
=
[];
List
<
Conversation
>
_conversations
=
[];
int
_currentIndex
=
0
;
int
_currentIndex
=
0
;
@override
bool
_hasSpeechPermission
=
false
;
void
initState
()
{
super
.
initState
();
@override
void
initState
()
{
_initializeStorage
();
super
.
initState
();
initSpeechToText
();
_initializeStorage
();
_initTts
();
initSpeechToText
();
}
_initTts
();
Future
<
void
>
_initializeStorage
()
async
{
}
_prefs
=
await
SharedPreferences
.
getInstance
();
Future
<
void
>
_initializeStorage
()
async
{
_storageService
=
StorageService
(
_prefs
);
_prefs
=
await
SharedPreferences
.
getInstance
();
_conversations
=
await
_storageService
.
getConversations
();
_storageService
=
StorageService
(
_prefs
);
// 每次进入应用创建一个新的会话
_conversations
=
await
_storageService
.
getConversations
();
_createNewConversation
();
// if (_conversations.isEmpty) {
// 每次进入应用创建一个新的会话
// _createNewConversation();
_createNewConversation
();
// } else {
// if (_conversations.isEmpty) {
// _currentConversation = _conversations.first;
// _createNewConversation();
// } else {
// setState(() {
// _currentConversation = _conversations.first;
// messages = _currentConversation.messages;
// });
// setState(() {
// }
// messages = _currentConversation.messages;
}
// });
// }
Future
<
void
>
_createNewConversation
()
async
{
}
final
newConversation
=
Conversation
(
id:
const
Uuid
().
v4
(),
Future
<
void
>
_createNewConversation
()
async
{
title:
'新会话
${_conversations.length + 1}
'
,
final
newConversation
=
Conversation
(
createdAt:
DateTime
.
now
(),
id:
const
Uuid
().
v4
(),
messages:
[],
title:
'新会话
${_conversations.length + 1}
'
,
);
createdAt:
DateTime
.
now
(),
messages:
[],
await
_storageService
.
addConversation
(
newConversation
);
);
setState
(()
{
await
_storageService
.
addConversation
(
newConversation
);
_conversations
.
insert
(
0
,
newConversation
);
setState
(()
{
_currentConversation
=
newConversation
;
_conversations
.
insert
(
0
,
newConversation
);
messages
=
[];
_currentConversation
=
newConversation
;
});
}
messages
=
[];
});
Future
<
void
>
_updateCurrentConversation
()
async
{
}
_currentConversation
=
Conversation
(
id:
_currentConversation
.
id
,
Future
<
void
>
_updateCurrentConversation
()
async
{
title:
_currentConversation
.
title
,
_currentConversation
=
Conversation
(
createdAt:
_currentConversation
.
createdAt
,
id:
_currentConversation
.
id
,
messages:
messages
,
title:
_currentConversation
.
title
,
);
createdAt:
_currentConversation
.
createdAt
,
messages:
messages
,
await
_storageService
.
updateConversation
(
_currentConversation
);
);
}
await
_storageService
.
updateConversation
(
_currentConversation
);
Future
<
void
>
_initTts
()
async
{
}
if
(!
kIsWeb
)
{
await
flutterTts
.
setSharedInstance
(
true
);
Future
<
void
>
_initTts
()
async
{
}
if
(!
kIsWeb
)
{
await
flutterTts
.
setSharedInstance
(
true
);
setState
(()
{});
}
}
setState
(()
{});
Future
<
void
>
initSpeechToText
()
async
{
}
await
speechToText
.
initialize
();
Future
<
void
>
initSpeechToText
()
async
{
setState
(()
{});
try
{
}
_hasSpeechPermission
=
await
speechToText
.
initialize
(
onStatus:
(
status
)
{
Future
<
void
>
startListening
()
async
{
print
(
'语音状态:
$status
'
);
await
speechToText
.
listen
(
onResult:
onSpeechResult
);
if
(
status
==
'notListening'
)
{
setState
(()
=>
_isListeningPressed
=
false
);
setState
(()
{});
}
}
},
onError:
(
error
)
{
Future
<
void
>
stopListening
()
async
{
print
(
'语音错误:
$error
'
);
await
speechToText
.
stop
();
setState
(()
=>
_isListeningPressed
=
false
);
_showErrorDialog
(
'语音初始化失败,请检查麦克风权限'
);
setState
(()
{});
},
}
);
}
catch
(
e
)
{
Future
<
void
>
onSpeechResult
(
SpeechRecognitionResult
result
)
async
{
print
(
'语音初始化错误:
$e
'
);
setState
(()
{
_hasSpeechPermission
=
false
;
lastWords
=
result
.
recognizedWords
;
}
setState
(()
{});
_currentVoiceText
=
result
.
recognizedWords
;
}
});
}
Future
<
void
>
startListening
()
async
{
if
(!
_hasSpeechPermission
)
{
Future
<
void
>
systemSpeak
(
String
content
)
async
{
_showErrorDialog
(
'请先授予麦克风权限'
);
try
{
setState
(()
=>
_isListeningPressed
=
false
);
if
(
kIsWeb
)
{
return
;
// 设置语速
}
await
flutterTts
.
setSpeechRate
(
1
);
try
{
await
speechToText
.
listen
(
// 音调
onResult:
onSpeechResult
,
localeId:
'zh_CN'
,
await
flutterTts
.
setPitch
(
0.8
);
listenMode:
ListenMode
.
confirmation
,
cancelOnError:
true
,
await
flutterTts
.
speak
(
content
);
partialResults:
true
,
}
else
{
);
await
flutterTts
.
setSharedInstance
(
true
);
}
catch
(
e
)
{
print
(
'开始录音失败:
$e
'
);
await
flutterTts
.
speak
(
content
);
setState
(()
=>
_isListeningPressed
=
false
);
}
_showErrorDialog
(
'启动语音识别失败,请重试'
);
}
catch
(
e
)
{
}
print
(
'TTS Error:
$e
'
);
}
}
}
Future
<
void
>
stopListening
()
async
{
await
speechToText
.
stop
();
Future
<
void
>
_sendMessage
()
async
{
if
(!
mounted
)
return
;
// 添加这行
setState
(()
{});
}
if
(
_messageController
.
text
.
isEmpty
)
return
;
Future
<
void
>
onSpeechResult
(
SpeechRecognitionResult
result
)
async
{
String
userMessage
=
_messageController
.
text
;
setState
(()
{
lastWords
=
result
.
recognizedWords
;
_messageController
.
clear
();
_currentVoiceText
=
result
.
recognizedWords
;
setState
(()
{
});
messages
.
add
(
ChatMessage
(
}
text:
userMessage
,
isUserMessage:
true
,
Future
<
void
>
systemSpeak
(
String
content
)
async
{
));
try
{
if
(
kIsWeb
)
{
currentStreamedContent
=
''
;
// 设置语速
_isLoading
=
true
;
await
flutterTts
.
setSpeechRate
(
1
);
});
// 音调
try
{
String
fullResponse
=
''
;
await
flutterTts
.
setPitch
(
0.8
);
bool
isFirstChunk
=
true
;
await
flutterTts
.
speak
(
content
);
}
else
{
// 创建一个缓冲区来存储收到的文本
await
flutterTts
.
setSharedInstance
(
true
);
StringBuffer
buffer
=
StringBuffer
();
await
flutterTts
.
speak
(
content
);
}
await
for
(
final
chunk
in
openAIService
.
chatGPTAPI
(
userMessage
))
{
}
catch
(
e
)
{
buffer
.
write
(
chunk
);
print
(
'TTS Error:
$e
'
);
}
// 逐字显示文本
}
for
(
int
i
=
fullResponse
.
length
;
i
<
buffer
.
length
;
i
++)
{
Future
<
void
>
_sendMessage
()
async
{
setState
(()
{
if
(!
mounted
)
return
;
// 添加这行
fullResponse
+=
buffer
.
toString
()[
i
];
if
(
_messageController
.
text
.
isEmpty
)
return
;
if
(
isFirstChunk
&&
i
==
0
)
{
messages
.
add
(
ChatMessage
(
String
userMessage
=
_messageController
.
text
;
text:
fullResponse
,
isUserMessage:
false
,
_messageController
.
clear
();
));
setState
(()
{
isFirstChunk
=
false
;
messages
.
add
(
ChatMessage
(
}
else
{
text:
userMessage
,
messages
.
last
=
ChatMessage
(
isUserMessage:
true
,
text:
fullResponse
,
));
isUserMessage:
false
,
);
currentStreamedContent
=
''
;
}
});
_isLoading
=
true
;
});
// 添加短暂延迟以创建打字效果
try
{
await
Future
.
delayed
(
const
Duration
(
milliseconds:
50
));
String
fullResponse
=
''
;
}
}
bool
isFirstChunk
=
true
;
//await systemSpeak(fullResponse);
// 创建一个缓冲区来存储收到的文本
await
_updateCurrentConversation
();
StringBuffer
buffer
=
StringBuffer
();
}
catch
(
e
)
{
setState
(()
{
await
for
(
final
chunk
in
openAIService
.
chatGPTAPI
(
userMessage
))
{
messages
.
add
(
ChatMessage
(
buffer
.
write
(
chunk
);
text:
'抱歉,出现了一些错误:
$e
'
,
isUserMessage:
false
,
// 逐字显示文本
));
});
for
(
int
i
=
fullResponse
.
length
;
i
<
buffer
.
length
;
i
++)
{
}
finally
{
setState
(()
{
setState
(()
{
fullResponse
+=
buffer
.
toString
()[
i
];
_isLoading
=
false
;
});
if
(
isFirstChunk
&&
i
==
0
)
{
}
messages
.
add
(
ChatMessage
(
}
text:
fullResponse
,
isUserMessage:
false
,
Future
<
void
>
_processAIResponse
(
String
userInput
)
async
{
));
try
{
String
fullResponse
=
''
;
isFirstChunk
=
false
;
}
else
{
bool
isFirstChunk
=
true
;
messages
.
last
=
ChatMessage
(
text:
fullResponse
,
// 创建一个缓冲区来储收到的文本
isUserMessage:
false
,
);
StringBuffer
buffer
=
StringBuffer
();
}
});
await
for
(
final
chunk
in
openAIService
.
chatGPTAPI
(
userInput
))
{
buffer
.
write
(
chunk
);
// 添加短暂延迟以创建打字效果
// 逐字显示文本
await
Future
.
delayed
(
const
Duration
(
milliseconds:
50
));
}
for
(
int
i
=
fullResponse
.
length
;
i
<
buffer
.
length
;
i
++)
{
}
setState
(()
{
fullResponse
+=
buffer
.
toString
()[
i
];
//await systemSpeak(fullResponse);
if
(
isFirstChunk
&&
i
==
0
)
{
await
_updateCurrentConversation
();
messages
.
add
(
ChatMessage
(
}
catch
(
e
)
{
text:
fullResponse
,
setState
(()
{
isUserMessage:
false
,
messages
.
add
(
ChatMessage
(
));
text:
'抱歉,出现了一些错误:
$e
'
,
isUserMessage:
false
,
isFirstChunk
=
false
;
));
}
else
{
});
messages
.
last
=
ChatMessage
(
}
finally
{
text:
fullResponse
,
setState
(()
{
isUserMessage:
false
,
_isLoading
=
false
;
);
});
}
}
});
}
// 添加短暂延迟以创建打字效果
Future
<
void
>
_processAIResponse
(
String
userInput
)
async
{
try
{
await
Future
.
delayed
(
const
Duration
(
milliseconds:
50
));
String
fullResponse
=
''
;
}
}
bool
isFirstChunk
=
true
;
// await systemSpeak(fullResponse);
// 创建一个缓冲区来储收到的文本
await
_updateCurrentConversation
();
StringBuffer
buffer
=
StringBuffer
();
}
catch
(
e
)
{
setState
(()
{
await
for
(
final
chunk
in
openAIService
.
chatGPTAPI
(
userInput
))
{
messages
.
add
(
ChatMessage
(
buffer
.
write
(
chunk
);
text:
'抱歉,出现了一些错误:
$e
'
,
isUserMessage:
false
,
// 逐字显示文本
));
});
for
(
int
i
=
fullResponse
.
length
;
i
<
buffer
.
length
;
i
++)
{
}
finally
{
setState
(()
{
setState
(()
{
fullResponse
+=
buffer
.
toString
()[
i
];
_isLoading
=
false
;
});
if
(
isFirstChunk
&&
i
==
0
)
{
}
messages
.
add
(
ChatMessage
(
}
text:
fullResponse
,
isUserMessage:
false
,
@override
));
void
dispose
()
{
super
.
dispose
();
isFirstChunk
=
false
;
}
else
{
speechToText
.
stop
();
messages
.
last
=
ChatMessage
(
text:
fullResponse
,
flutterTts
.
stop
();
isUserMessage:
false
,
);
_messageController
.
dispose
();
}
}
});
Widget
_buildBottomInput
()
{
// 添加短暂延迟以创建打字效果
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
await
Future
.
delayed
(
const
Duration
(
milliseconds:
50
));
decoration:
BoxDecoration
(
}
color:
Colors
.
white
,
}
boxShadow:
[
BoxShadow
(
// await systemSpeak(fullResponse);
color:
Colors
.
grey
.
withOpacity
(
0.1
),
spreadRadius:
1
,
await
_updateCurrentConversation
();
blurRadius:
3
,
}
catch
(
e
)
{
offset:
const
Offset
(
0
,
-
1
),
setState
(()
{
),
messages
.
add
(
ChatMessage
(
],
text:
'抱歉,出现了一些错误:
$e
'
,
),
isUserMessage:
false
,
child:
Row
(
));
children:
[
});
Expanded
(
}
finally
{
child:
_isVoiceMode
setState
(()
{
?
GestureDetector
(
_isLoading
=
false
;
onLongPressStart:
(
_
)
async
{
});
setState
(()
{
}
_isListeningPressed
=
true
;
}
_currentVoiceText
=
''
;
@override
});
void
dispose
()
{
super
.
dispose
();
await
startListening
();
},
speechToText
.
stop
();
onLongPressEnd:
(
_
)
async
{
setState
(()
=>
_isListeningPressed
=
false
);
flutterTts
.
stop
();
await
stopListening
();
_messageController
.
dispose
();
}
final
finalVoiceText
=
_currentVoiceText
;
Widget
_buildBottomInput
()
{
if
(
finalVoiceText
.
isNotEmpty
)
{
return
Container
(
setState
(()
{
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
8
),
messages
.
add
(
ChatMessage
(
decoration:
BoxDecoration
(
text:
finalVoiceText
,
color:
Colors
.
white
,
isUserMessage:
true
,
boxShadow:
[
));
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
_isLoading
=
true
;
spreadRadius:
1
,
});
blurRadius:
3
,
offset:
const
Offset
(
0
,
-
1
),
await
_processAIResponse
(
finalVoiceText
);
),
}
],
),
setState
(()
{
child:
Row
(
_currentVoiceText
=
''
;
children:
[
});
Expanded
(
},
child:
_isVoiceMode
child:
Container
(
?
GestureDetector
(
padding:
const
EdgeInsets
.
symmetric
(
onTap:
()
async
{
horizontal:
20
,
if
(!
_isListeningPressed
)
{
vertical:
10
,
// 开始录音
),
setState
(()
{
decoration:
BoxDecoration
(
_isListeningPressed
=
true
;
color:
Colors
.
grey
[
100
],
_currentVoiceText
=
''
;
borderRadius:
BorderRadius
.
circular
(
25
),
});
),
await
startListening
();
child:
Text
(
}
else
{
_isListeningPressed
// 停止录音
?
(
_currentVoiceText
.
isEmpty
setState
(()
=>
_isListeningPressed
=
false
);
?
'正在聆听...'
await
stopListening
();
:
_currentVoiceText
)
:
'按住说话'
,
// 确保有语音文本才处理
textAlign:
TextAlign
.
center
,
if
(
_currentVoiceText
.
isNotEmpty
)
{
style:
TextStyle
(
setState
(()
{
color:
_isListeningPressed
messages
.
add
(
ChatMessage
(
?
Pallete
.
firstSuggestionBoxColor
text:
_currentVoiceText
,
:
Colors
.
grey
[
600
],
isUserMessage:
true
,
),
));
),
_isLoading
=
true
;
),
});
)
await
_processAIResponse
(
_currentVoiceText
);
:
TextField
(
}
controller:
_messageController
,
setState
(()
=>
_currentVoiceText
=
''
);
decoration:
InputDecoration
(
}
hintText:
'输入消息...'
,
},
border:
OutlineInputBorder
(
child:
Container
(
borderRadius:
BorderRadius
.
circular
(
25
),
padding:
const
EdgeInsets
.
symmetric
(
borderSide:
BorderSide
.
none
,
horizontal:
20
,
),
vertical:
10
,
filled:
true
,
),
fillColor:
Colors
.
grey
[
100
],
decoration:
BoxDecoration
(
contentPadding:
const
EdgeInsets
.
symmetric
(
color:
Colors
.
grey
[
100
],
horizontal:
20
,
borderRadius:
BorderRadius
.
circular
(
25
),
vertical:
10
,
),
),
child:
Row
(
),
mainAxisSize:
MainAxisSize
.
min
,
),
children:
[
),
Icon
(
const
SizedBox
(
width:
8
),
_isListeningPressed
?
Icons
.
stop
:
Icons
.
mic
,
IconButton
(
color:
_isListeningPressed
icon:
Icon
(
?
Pallete
.
firstSuggestionBoxColor
_isVoiceMode
?
Icons
.
keyboard
:
Icons
.
mic
,
:
Colors
.
grey
[
600
],
color:
Pallete
.
firstSuggestionBoxColor
,
),
),
const
SizedBox
(
width:
8
),
onPressed:
()
{
Text
(
setState
(()
=>
_isVoiceMode
=
!
_isVoiceMode
);
_isListeningPressed
},
?
(
_currentVoiceText
.
isEmpty
?
'点击停止'
:
_currentVoiceText
)
),
:
'点击说话'
,
if
(!
_isVoiceMode
)
textAlign:
TextAlign
.
center
,
IconButton
(
style:
TextStyle
(
icon:
const
Icon
(
color:
_isListeningPressed
Icons
.
send
,
?
Pallete
.
firstSuggestionBoxColor
color:
Pallete
.
firstSuggestionBoxColor
,
:
Colors
.
grey
[
600
],
),
),
onPressed:
_sendMessage
,
),
),
],
],
),
),
),
);
)
}
:
TextField
(
controller:
_messageController
,
AppBar
_buildAppBar
()
{
decoration:
InputDecoration
(
return
AppBar
(
hintText:
'输入消息...'
,
title:
Row
(
border:
OutlineInputBorder
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
borderRadius:
BorderRadius
.
circular
(
25
),
children:
[
borderSide:
BorderSide
.
none
,
TextButton
(
),
onPressed:
()
{
filled:
true
,
setState
(()
=>
_currentIndex
=
0
);
fillColor:
Colors
.
grey
[
100
],
},
contentPadding:
const
EdgeInsets
.
symmetric
(
child:
Text
(
horizontal:
20
,
'对话'
,
vertical:
10
,
style:
TextStyle
(
),
fontSize:
_currentIndex
==
0
?
20
:
16
,
),
fontWeight:
),
_currentIndex
==
0
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
color:
_currentIndex
==
0
const
SizedBox
(
width:
8
),
?
Pallete
.
firstSuggestionBoxColor
IconButton
(
:
Colors
.
grey
,
icon:
Icon
(
),
_isVoiceMode
?
Icons
.
keyboard
:
Icons
.
mic
,
),
color:
Pallete
.
firstSuggestionBoxColor
,
),
),
const
SizedBox
(
width:
20
),
onPressed:
()
{
TextButton
(
setState
(()
=>
_isVoiceMode
=
!
_isVoiceMode
);
onPressed:
()
{
},
setState
(()
=>
_currentIndex
=
1
);
),
},
if
(!
_isVoiceMode
)
child:
Text
(
IconButton
(
'应用'
,
icon:
const
Icon
(
style:
TextStyle
(
Icons
.
send
,
fontSize:
_currentIndex
==
1
?
20
:
16
,
color:
Pallete
.
firstSuggestionBoxColor
,
fontWeight:
),
_currentIndex
==
1
?
FontWeight
.
bold
:
FontWeight
.
normal
,
onPressed:
_sendMessage
,
color:
_currentIndex
==
1
),
?
Pallete
.
firstSuggestionBoxColor
],
:
Colors
.
grey
,
),
),
);
),
}
),
],
AppBar
_buildAppBar
()
{
),
return
AppBar
(
leading:
Builder
(
title:
Row
(
builder:
(
context
)
=>
IconButton
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
icon:
const
Icon
(
Icons
.
menu
),
children:
[
onPressed:
()
=>
Scaffold
.
of
(
context
).
openDrawer
(),
TextButton
(
),
onPressed:
()
{
),
setState
(()
=>
_currentIndex
=
0
);
);
},
}
child:
Text
(
'对话'
,
Widget
_buildDrawer
(
BuildContext
context
)
{
style:
TextStyle
(
return
Drawer
(
fontSize:
_currentIndex
==
0
?
20
:
16
,
child:
Column
(
fontWeight:
children:
[
_currentIndex
==
0
?
FontWeight
.
bold
:
FontWeight
.
normal
,
DrawerHeader
(
color:
_currentIndex
==
0
decoration:
BoxDecoration
(
?
Pallete
.
firstSuggestionBoxColor
color:
Pallete
.
firstSuggestionBoxColor
,
:
Colors
.
grey
,
),
),
child:
const
Center
(
),
child:
Text
(
),
'会话列表'
,
const
SizedBox
(
width:
20
),
style:
TextStyle
(
TextButton
(
color:
Colors
.
white
,
onPressed:
()
{
fontSize:
24
,
setState
(()
=>
_currentIndex
=
1
);
),
},
),
child:
Text
(
),
'应用'
,
),
style:
TextStyle
(
ListTile
(
fontSize:
_currentIndex
==
1
?
20
:
16
,
leading:
const
Icon
(
Icons
.
add
),
fontWeight:
title:
const
Text
(
'新建会话'
),
_currentIndex
==
1
?
FontWeight
.
bold
:
FontWeight
.
normal
,
onTap:
()
{
color:
_currentIndex
==
1
_createNewConversation
();
?
Pallete
.
firstSuggestionBoxColor
:
Colors
.
grey
,
Navigator
.
pop
(
context
);
),
},
),
),
),
Expanded
(
],
child:
ListView
.
builder
(
),
itemCount:
_conversations
.
length
,
leading:
Builder
(
itemBuilder:
(
context
,
index
)
{
builder:
(
context
)
=>
IconButton
(
final
conversation
=
_conversations
[
index
];
icon:
const
Icon
(
Icons
.
menu
),
onPressed:
()
=>
Scaffold
.
of
(
context
).
openDrawer
(),
return
ListTile
(
),
leading:
const
Icon
(
Icons
.
chat
),
),
title:
Text
(
conversation
.
title
),
);
subtitle:
Text
(
}
conversation
.
messages
.
isEmpty
?
'暂无消息'
Widget
_buildDrawer
(
BuildContext
context
)
{
:
conversation
.
messages
.
last
.
text
,
return
Drawer
(
maxLines:
1
,
child:
Column
(
overflow:
TextOverflow
.
ellipsis
,
children:
[
),
DrawerHeader
(
selected:
_currentConversation
.
id
==
conversation
.
id
,
decoration:
BoxDecoration
(
onTap:
()
{
color:
Pallete
.
firstSuggestionBoxColor
,
setState
(()
{
),
_currentConversation
=
conversation
;
child:
const
Center
(
child:
Text
(
messages
=
conversation
.
messages
;
'会话列表'
,
});
style:
TextStyle
(
color:
Colors
.
white
,
Navigator
.
pop
(
context
);
fontSize:
24
,
},
),
trailing:
IconButton
(
),
icon:
const
Icon
(
Icons
.
delete
),
),
onPressed:
()
async
{
),
await
_storageService
.
deleteConversation
(
conversation
.
id
);
ListTile
(
leading:
const
Icon
(
Icons
.
add
),
setState
(()
{
title:
const
Text
(
'新建会话'
),
_conversations
.
removeAt
(
index
);
onTap:
()
{
_createNewConversation
();
if
(
_currentConversation
.
id
==
conversation
.
id
)
{
if
(
_conversations
.
isEmpty
)
{
Navigator
.
pop
(
context
);
_createNewConversation
();
},
}
else
{
),
_currentConversation
=
_conversations
.
first
;
Expanded
(
child:
ListView
.
builder
(
messages
=
_currentConversation
.
messages
;
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
),
?
'暂无消息'
const
Spacer
(),
:
conversation
.
messages
.
last
.
text
,
const
Divider
(
height:
1
),
maxLines:
1
,
FutureBuilder
<
User
?>(
overflow:
TextOverflow
.
ellipsis
,
future:
StorageService
.
getUser
(),
),
builder:
(
context
,
snapshot
)
{
selected:
_currentConversation
.
id
==
conversation
.
id
,
if
(!
snapshot
.
hasData
)
return
const
SizedBox
();
onTap:
()
{
setState
(()
{
return
Container
(
_currentConversation
=
conversation
;
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
messages
=
conversation
.
messages
;
color:
Colors
.
grey
[
50
],
});
),
child:
Column
(
Navigator
.
pop
(
context
);
children:
[
},
Row
(
trailing:
IconButton
(
children:
[
icon:
const
Icon
(
Icons
.
delete
),
Container
(
onPressed:
()
async
{
width:
50
,
await
_storageService
.
deleteConversation
(
conversation
.
id
);
height:
50
,
decoration:
BoxDecoration
(
setState
(()
{
color:
Pallete
.
firstSuggestionBoxColor
,
_conversations
.
removeAt
(
index
);
shape:
BoxShape
.
circle
,
),
if
(
_currentConversation
.
id
==
conversation
.
id
)
{
child:
Center
(
if
(
_conversations
.
isEmpty
)
{
child:
Text
(
_createNewConversation
();
snapshot
.
data
!.
username
[
0
].
toUpperCase
(),
}
else
{
style:
const
TextStyle
(
_currentConversation
=
_conversations
.
first
;
color:
Colors
.
white
,
fontSize:
24
,
messages
=
_currentConversation
.
messages
;
fontWeight:
FontWeight
.
bold
,
}
),
}
),
});
),
},
),
),
const
SizedBox
(
width:
16
),
);
Expanded
(
},
child:
Column
(
),
crossAxisAlignment:
CrossAxisAlignment
.
start
,
),
children:
[
const
Spacer
(),
Text
(
const
Divider
(
height:
1
),
snapshot
.
data
!.
username
,
FutureBuilder
<
User
?>(
style:
const
TextStyle
(
future:
StorageService
.
getUser
(),
fontSize:
18
,
builder:
(
context
,
snapshot
)
{
fontWeight:
FontWeight
.
bold
,
if
(!
snapshot
.
hasData
)
return
const
SizedBox
();
),
),
return
Container
(
const
SizedBox
(
height:
4
),
padding:
const
EdgeInsets
.
all
(
16
),
Text
(
decoration:
BoxDecoration
(
'在线'
,
color:
Colors
.
grey
[
50
],
style:
TextStyle
(
),
fontSize:
14
,
child:
Column
(
color:
Colors
.
green
[
600
],
children:
[
),
Row
(
),
children:
[
],
Container
(
),
width:
50
,
),
height:
50
,
],
decoration:
BoxDecoration
(
),
color:
Pallete
.
firstSuggestionBoxColor
,
const
SizedBox
(
height:
16
),
shape:
BoxShape
.
circle
,
InkWell
(
),
onTap:
()
async
{
child:
Center
(
final
confirmed
=
await
showDialog
<
bool
>(
child:
Text
(
context:
context
,
snapshot
.
data
!.
username
[
0
].
toUpperCase
(),
builder:
(
context
)
=>
AlertDialog
(
style:
const
TextStyle
(
title:
const
Text
(
'确认退出'
),
color:
Colors
.
white
,
content:
const
Text
(
'您确定要退出登录吗?'
),
fontSize:
24
,
actions:
[
fontWeight:
FontWeight
.
bold
,
TextButton
(
),
onPressed:
()
=>
Navigator
.
pop
(
context
,
false
),
),
child:
const
Text
(
'取消'
),
),
),
),
TextButton
(
const
SizedBox
(
width:
16
),
onPressed:
()
=>
Navigator
.
pop
(
context
,
true
),
Expanded
(
child:
const
Text
(
child:
Column
(
'退出'
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
style:
TextStyle
(
color:
Colors
.
red
),
children:
[
),
Text
(
),
snapshot
.
data
!.
username
,
],
style:
const
TextStyle
(
),
fontSize:
18
,
);
fontWeight:
FontWeight
.
bold
,
),
if
(
confirmed
==
true
&&
context
.
mounted
)
{
),
await
StorageService
.
clearUser
();
const
SizedBox
(
height:
4
),
Text
(
Navigator
.
of
(
context
).
pushReplacementNamed
(
'/login'
);
'在线'
,
}
style:
TextStyle
(
},
fontSize:
14
,
child:
Container
(
color:
Colors
.
green
[
600
],
padding:
const
EdgeInsets
.
symmetric
(
),
vertical:
12
,
),
horizontal:
16
,
],
),
),
decoration:
BoxDecoration
(
),
color:
Colors
.
red
[
50
],
],
borderRadius:
BorderRadius
.
circular
(
8
),
),
),
const
SizedBox
(
height:
16
),
child:
Row
(
InkWell
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
onTap:
()
async
{
children:
[
final
confirmed
=
await
showDialog
<
bool
>(
Icon
(
context:
context
,
Icons
.
logout
,
builder:
(
context
)
=>
AlertDialog
(
color:
Colors
.
red
[
700
],
title:
const
Text
(
'确认退出'
),
size:
20
,
content:
const
Text
(
'您确定要退出登录吗?'
),
),
actions:
[
const
SizedBox
(
width:
8
),
TextButton
(
Text
(
onPressed:
()
=>
Navigator
.
pop
(
context
,
false
),
'退出登录'
,
child:
const
Text
(
'取消'
),
style:
TextStyle
(
),
color:
Colors
.
red
[
700
],
TextButton
(
fontSize:
16
,
onPressed:
()
=>
Navigator
.
pop
(
context
,
true
),
fontWeight:
FontWeight
.
w500
,
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
(
@override
vertical:
12
,
Widget
build
(
BuildContext
context
)
{
horizontal:
16
,
return
Scaffold
(
),
appBar:
widget
.
hideNavigation
?
null
:
_buildAppBar
(),
decoration:
BoxDecoration
(
drawer:
widget
.
hideNavigation
?
null
:
_buildDrawer
(
context
),
color:
Colors
.
red
[
50
],
body:
IndexedStack
(
borderRadius:
BorderRadius
.
circular
(
8
),
index:
_currentIndex
,
),
children:
[
child:
Row
(
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
children:
[
Expanded
(
Icon
(
child:
ListView
.
builder
(
Icons
.
logout
,
padding:
const
EdgeInsets
.
all
(
16
),
color:
Colors
.
red
[
700
],
itemCount:
messages
.
isEmpty
?
1
:
messages
.
length
,
size:
20
,
itemBuilder:
(
context
,
index
)
{
),
if
(
messages
.
isEmpty
)
{
const
SizedBox
(
width:
8
),
return
Center
(
Text
(
child:
Column
(
'出登录'
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
style:
TextStyle
(
children:
[
color:
Colors
.
red
[
700
],
ZoomIn
(
fontSize:
16
,
child:
Stack
(
fontWeight:
FontWeight
.
w500
,
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
(
void
_showErrorDialog
(
String
message
)
{
image:
widget
.
customImageUrl
!=
null
showDialog
(
?
NetworkImage
(
context:
context
,
widget
.
customImageUrl
!)
builder:
(
context
)
=>
AlertDialog
(
:
const
AssetImage
(
title:
const
Text
(
'提示'
),
'assets/images/virtualAssistant.png'
,
content:
Text
(
message
),
)
as
ImageProvider
,
actions:
[
onError:
(
exception
,
stackTrace
)
{
TextButton
(
print
(
onPressed:
()
=>
Navigator
.
pop
(
context
),
'Error loading image:
$exception
'
);
child:
const
Text
(
'确定'
),
},
),
),
],
),
),
),
);
],
}
),
),
@override
const
SizedBox
(
height:
20
),
Widget
build
(
BuildContext
context
)
{
Text
(
return
Scaffold
(
widget
.
customDescription
??
appBar:
widget
.
hideNavigation
?
null
:
_buildAppBar
(),
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?'
,
drawer:
widget
.
hideNavigation
?
null
:
_buildDrawer
(
context
),
style:
const
TextStyle
(
body:
IndexedStack
(
fontSize:
20
,
index:
_currentIndex
,
color:
Pallete
.
mainFontColor
,
children:
[
),
Column
(
textAlign:
TextAlign
.
center
,
children:
[
),
Expanded
(
],
child:
ListView
.
builder
(
),
padding:
const
EdgeInsets
.
all
(
16
),
);
itemCount:
messages
.
isEmpty
?
1
:
messages
.
length
,
}
itemBuilder:
(
context
,
index
)
{
if
(
messages
.
isEmpty
)
{
final
message
=
messages
[
index
];
return
Center
(
child:
Column
(
return
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
children:
[
ChatBubble
(
ZoomIn
(
message:
message
,
child:
Stack
(
isTyping:
_isLoading
&&
children:
[
index
==
messages
.
length
-
1
&&
Center
(
!
message
.
isUserMessage
,
child:
Container
(
),
height:
120
,
if
(
_isLoading
&&
width:
120
,
index
==
messages
.
length
-
1
&&
margin:
const
EdgeInsets
.
only
(
top:
4
),
message
.
isUserMessage
)
decoration:
const
BoxDecoration
(
Padding
(
color:
Pallete
.
assistantCircleColor
,
padding:
const
EdgeInsets
.
all
(
8.0
),
shape:
BoxShape
.
circle
,
child:
Align
(
),
alignment:
Alignment
.
centerLeft
,
),
child:
Row
(
),
mainAxisSize:
MainAxisSize
.
min
,
Container
(
children:
[
height:
123
,
SizedBox
(
decoration:
BoxDecoration
(
width:
20
,
shape:
BoxShape
.
circle
,
height:
20
,
image:
DecorationImage
(
child:
CircularProgressIndicator
(
image:
widget
.
customImageUrl
!=
null
strokeWidth:
2
,
?
NetworkImage
(
valueColor:
AlwaysStoppedAnimation
<
Color
>(
widget
.
customImageUrl
!)
Pallete
.
firstSuggestionBoxColor
,
:
const
AssetImage
(
),
'assets/images/virtualAssistant.png'
,
),
)
as
ImageProvider
,
),
onError:
(
exception
,
stackTrace
)
{
const
SizedBox
(
width:
8
),
print
(
const
Text
(
'Error loading image:
$exception
'
);
'正在思考中...'
,
},
style:
TextStyle
(
),
color:
Pallete
.
mainFontColor
,
),
fontSize:
14
,
),
),
],
),
),
],
),
),
const
SizedBox
(
height:
20
),
),
Text
(
),
widget
.
customDescription
??
],
'你好!我是你的快际新云AI助手,请问有什么可以帮你的吗?'
,
);
style:
const
TextStyle
(
},
fontSize:
20
,
),
color:
Pallete
.
mainFontColor
,
),
),
_buildBottomInput
(),
textAlign:
TextAlign
.
center
,
],
),
),
],
if
(!
widget
.
hideNavigation
)
AppsPage
(),
),
],
);
),
}
);
}
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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论