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
28ae4749
提交
28ae4749
authored
11月 08, 2024
作者:
songchuancai
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
增加应用列表
上级
e92fe5f9
隐藏空白字符变更
内嵌
并排
正在显示
9 个修改的文件
包含
1073 行增加
和
3326 行删除
+1073
-3326
home_page.dart
lib/home_page.dart
+908
-3217
app_item.dart
lib/models/app_item.dart
+22
-4
apps_page.dart
lib/pages/apps_page.dart
+62
-38
chat_page.dart
lib/pages/chat_page.dart
+48
-56
secrets.dart
lib/secrets.dart
+1
-0
api_service.dart
lib/services/api_service.dart
+1
-3
apps_service.dart
lib/services/apps_service.dart
+29
-0
chat_service.dart
lib/services/chat_service.dart
+2
-3
pubspec.yaml
pubspec.yaml
+0
-5
没有找到文件。
lib/home_page.dart
浏览文件 @
28ae4749
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
'package:flutter_markdown/flutter_markdown.dart'
;
import
'pages/apps_page.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
();
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
(
3
);
// 音调
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
(
_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
?
AssetImage
(
widget
.
customImageUrl
!)
:
const
AssetImage
(
'assets/images/virtualAssistant.png'
,
),
),
),
),
],
),
),
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:
[
Align
(
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
&&
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
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
'package:flutter_markdown/flutter_markdown.dart'
;
import
'pages/apps_page.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
();
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
(
3
);
// 音调
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
?
AssetImage
(
widget
.
customImageUrl
!)
:
const
AssetImage
(
'assets/images/virtualAssistant.png'
,
),
),
),
),
],
),
),
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:
[
Align
(
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
&&
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
(),
],
),
);
}
}
lib/models/app_item.dart
浏览文件 @
28ae4749
class
AppItem
{
final
String
id
;
final
String
name
;
final
String
description
;
final
String
imageUrl
;
final
String
iconUrl
;
final
String
?
appCode
;
final
String
openingStatement
;
final
List
<
String
>
suggestedQuestions
;
final
bool
isDefault
;
AppItem
({
required
this
.
id
,
required
this
.
name
,
required
this
.
description
,
required
this
.
imageUrl
,
required
this
.
iconUrl
,
this
.
appCode
,
required
this
.
openingStatement
,
required
this
.
suggestedQuestions
,
required
this
.
isDefault
,
});
factory
AppItem
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
AppItem
(
name:
json
[
'name'
]
??
''
,
description:
json
[
'description'
]
??
''
,
iconUrl:
json
[
'icon_url'
]
??
''
,
appCode:
json
[
'app_code'
],
openingStatement:
json
[
'opening_statement'
]
??
''
,
suggestedQuestions:
List
<
String
>.
from
(
json
[
'suggested_questions'
]
??
[]),
isDefault:
json
[
'is_default'
]
??
false
,
);
}
}
lib/pages/apps_page.dart
浏览文件 @
28ae4749
import
'package:flutter/material.dart'
;
import
'../services/apps_service.dart'
;
import
'../models/app_item.dart'
;
import
'../pallete.dart'
;
import
'../services/storage_service.dart'
;
import
'../models/user.dart'
;
class
AppsPage
extends
State
less
Widget
{
AppsPage
({
super
.
key
});
class
AppsPage
extends
State
ful
Widget
{
const
AppsPage
({
super
.
key
});
final
List
<
AppItem
>
apps
=
[
AppItem
(
id:
'1'
,
name:
'文章助手'
,
description:
'帮助您撰写高质量的文章,提供创意和灵感'
,
imageUrl:
'assets/images/article.png'
,
),
AppItem
(
id:
'2'
,
name:
'代码专家'
,
description:
'解答编程问题,优化代码结构,提供最佳实践'
,
imageUrl:
'assets/images/code.png'
,
),
AppItem
(
id:
'3'
,
name:
'翻译助手'
,
description:
'精准翻译多国语言,支持专业术语翻译'
,
imageUrl:
'assets/images/translate.png'
,
),
AppItem
(
id:
'4'
,
name:
'数学导师'
,
description:
'解决数学问题,讲解数学概念和公式'
,
imageUrl:
'assets/images/math.png'
,
),
AppItem
(
id:
'5'
,
name:
'生活顾问'
,
description:
'提供日常生活建议,解答各类生活问题'
,
imageUrl:
'assets/images/life.png'
,
),
];
@override
State
<
AppsPage
>
createState
()
=>
_AppsPageState
();
}
class
_AppsPageState
extends
State
<
AppsPage
>
{
late
final
AppsService
_appsService
;
List
<
AppItem
>
apps
=
[];
bool
isLoading
=
true
;
String
?
error
;
@override
void
initState
()
{
super
.
initState
();
_initializeService
();
}
Future
<
void
>
_initializeService
()
async
{
final
User
?
user
=
await
StorageService
.
getUser
();
_appsService
=
AppsService
(
token:
user
?.
token
);
fetchApps
();
}
Future
<
void
>
fetchApps
()
async
{
try
{
final
appsList
=
await
_appsService
.
getApps
();
setState
(()
{
apps
=
appsList
;
isLoading
=
false
;
});
}
catch
(
e
)
{
setState
(()
{
error
=
e
.
toString
();
isLoading
=
false
;
});
}
}
@override
Widget
build
(
BuildContext
context
)
{
if
(
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
error
!=
null
)
{
return
Center
(
child:
Text
(
error
!));
}
return
ListView
.
builder
(
padding:
const
EdgeInsets
.
all
(
16
),
itemCount:
apps
.
length
,
...
...
@@ -57,10 +72,19 @@ class AppsPage extends StatelessWidget {
color:
Pallete
.
firstSuggestionBoxColor
.
withOpacity
(
0.1
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Icon
(
Icons
.
apps
,
size:
30
,
color:
Pallete
.
firstSuggestionBoxColor
,
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
app
.
iconUrl
,
fit:
BoxFit
.
cover
,
errorBuilder:
(
context
,
error
,
stackTrace
)
{
return
Icon
(
Icons
.
apps
,
size:
30
,
color:
Pallete
.
firstSuggestionBoxColor
,
);
},
),
),
),
title:
Text
(
...
...
lib/pages/chat_page.dart
浏览文件 @
28ae4749
import
'package:flutter/material.dart'
;
import
'../models/app_item.dart'
;
import
'../home_page.dart'
;
class
ChatPage
extends
StatelessWidget
{
final
AppItem
app
;
const
ChatPage
({
super
.
key
,
required
this
.
app
});
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
title:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
app
.
name
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
),
),
Text
(
app
.
description
,
style:
const
TextStyle
(
fontSize:
12
,
fontWeight:
FontWeight
.
normal
,
),
),
],
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
),
body:
HomePage
(
customTitle:
app
.
name
,
customDescription:
app
.
description
,
customImageUrl:
app
.
imageUrl
,
hideNavigation:
true
,
),
);
}
}
\ No newline at end of file
import
'package:flutter/material.dart'
;
import
'../models/app_item.dart'
;
import
'../home_page.dart'
;
class
ChatPage
extends
StatelessWidget
{
final
AppItem
app
;
const
ChatPage
({
super
.
key
,
required
this
.
app
});
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
title:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
app
.
name
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
),
),
Text
(
app
.
description
,
style:
const
TextStyle
(
fontSize:
12
,
fontWeight:
FontWeight
.
normal
,
),
),
],
),
leading:
IconButton
(
icon:
const
Icon
(
Icons
.
arrow_back
),
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(),
),
),
body:
HomePage
(
customTitle:
app
.
name
,
customDescription:
app
.
description
,
customImageUrl:
app
.
iconUrl
,
hideNavigation:
true
,
),
);
}
}
lib/secrets.dart
浏览文件 @
28ae4749
const
openAIAPIKey
=
'sk-zV0MPjPserDD4gdQ8lE8aUGwxayOwuayogGidos4VO8uxdDL'
;
const
baseUrl
=
'https://knowledge-web.apps.iytcloud.com/console/api'
;
lib/services/api_service.dart
浏览文件 @
28ae4749
import
'dart:convert'
;
import
'package:http/http.dart'
as
http
;
import
'../secrets.dart'
;
class
ApiService
{
static
const
String
baseUrl
=
'https://knowledge-web.apps.iytcloud.com/console/api'
;
static
Future
<
Map
<
String
,
dynamic
>>
login
(
String
email
,
String
password
)
async
{
final
response
=
await
http
.
post
(
...
...
lib/services/apps_service.dart
0 → 100644
浏览文件 @
28ae4749
import
'dart:convert'
;
import
'package:http/http.dart'
as
http
;
import
'../models/app_item.dart'
;
import
'../secrets.dart'
;
class
AppsService
{
final
String
?
token
;
AppsService
({
this
.
token
});
Future
<
List
<
AppItem
>>
getApps
({
int
page
=
1
,
int
limit
=
12
})
async
{
final
response
=
await
http
.
get
(
Uri
.
parse
(
'
$baseUrl
/apps/?page=
$page
&limit=
$limit
'
),
headers:
{
'Content-Type'
:
'application/json'
,
'Accept'
:
'application/json'
,
if
(
token
!=
null
)
'Authorization'
:
'Bearer
$token
'
,
},
);
if
(
response
.
statusCode
==
200
)
{
final
Map
<
String
,
dynamic
>
responseData
=
json
.
decode
(
response
.
body
);
final
List
<
dynamic
>
appsData
=
responseData
[
'data'
];
return
appsData
.
map
((
json
)
=>
AppItem
.
fromJson
(
json
)).
toList
();
}
else
{
throw
Exception
(
'获取应用列表失败:
${response.statusCode}
'
);
}
}
}
lib/services/chat_service.dart
浏览文件 @
28ae4749
import
'dart:convert'
;
import
'package:http/http.dart'
as
http
;
import
'../secrets.dart'
;
class
OpenAIService
{
final
String
baseUrl
=
'https://knowledge-web.apps.iytcloud.com/console/api/openapi/chat'
;
final
String
apiKey
=
'sk-OVjS7VE9mT68Uvg7kSFoMnbU6EU836FO'
;
final
String
appKey
=
'app-FRP2s2wSx01rsE67'
;
String
?
conversationId
;
...
...
@@ -13,7 +12,7 @@ class OpenAIService {
var
buffer
=
StringBuffer
();
try
{
final
request
=
http
.
Request
(
'POST'
,
Uri
.
parse
(
baseUrl
));
final
request
=
http
.
Request
(
'POST'
,
Uri
.
parse
(
'
$baseUrl
/openapi/chat'
));
request
.
headers
.
addAll
({
'Content-Type'
:
'application/json'
,
'Authorization'
:
'Bearer
$apiKey
'
,
...
...
pubspec.yaml
浏览文件 @
28ae4749
...
...
@@ -70,11 +70,6 @@ flutter:
assets
:
-
assets/images/
-
assets/sounds/
-
assets/images/article.png
-
assets/images/code.png
-
assets/images/translate.png
-
assets/images/math.png
-
assets/images/life.png
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论