view.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import 'package:electronic_assistant/base/base_page.dart';
  2. import 'package:electronic_assistant/data/bean/chat_item.dart';
  3. import 'package:electronic_assistant/module/chat/controller.dart';
  4. import 'package:electronic_assistant/resource/colors.gen.dart';
  5. import 'package:electronic_assistant/utils/expand.dart';
  6. import 'package:flutter/cupertino.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter_screenutil/flutter_screenutil.dart';
  10. import 'package:get/get.dart';
  11. import 'package:pull_to_refresh/pull_to_refresh.dart';
  12. import '../../data/bean/progressing_chat_item.dart';
  13. import '../../resource/assets.gen.dart';
  14. class ChatPage extends BasePage<ChatController> {
  15. const ChatPage({super.key});
  16. @override
  17. bool immersive() {
  18. return true;
  19. }
  20. @override
  21. Widget buildBody(BuildContext context) {
  22. // 第一次启动时弹出定制窗口
  23. controller.showStartSheet(context);
  24. return Stack(
  25. children: [
  26. buildBackgroundGradient(),
  27. buildTopGradient(),
  28. Scaffold(
  29. backgroundColor: Colors.transparent,
  30. appBar: AppBar(
  31. leading: IconButton(
  32. icon: const Icon(Icons.arrow_back_ios),
  33. onPressed: () {
  34. Navigator.pop(context);
  35. },
  36. ),
  37. scrolledUnderElevation: 0,
  38. backgroundColor: Colors.transparent,
  39. systemOverlayStyle: SystemUiOverlayStyle.dark,
  40. centerTitle: true,
  41. title: IntrinsicWidth(
  42. child: Row(
  43. mainAxisAlignment: MainAxisAlignment.center,
  44. children: [
  45. Image(
  46. image: Assets.images.iconChatXiaoTin.provider(),
  47. width: 28.w,
  48. height: 28.w),
  49. Container(
  50. margin: EdgeInsets.only(left: 6.w),
  51. child: Text('聊天',
  52. style: TextStyle(
  53. fontSize: 16.w,
  54. fontWeight: FontWeight.bold,
  55. color: ColorName.primaryTextColor))),
  56. ],
  57. ),
  58. ),
  59. ),
  60. body: buildBodyContent(),
  61. )
  62. ],
  63. );
  64. }
  65. Widget buildBodyContent() {
  66. return Column(
  67. children: [
  68. Expanded(
  69. child: Container(
  70. padding: EdgeInsets.symmetric(horizontal: 12.w),
  71. child: Obx(() {
  72. return SmartRefresher(
  73. controller: controller.refreshController,
  74. footer: CustomFooter(
  75. loadStyle: LoadStyle.ShowWhenLoading,
  76. builder: (context, mode) {
  77. if (mode == LoadStatus.loading ||
  78. mode == LoadStatus.canLoading) {
  79. return const SizedBox(
  80. height: 60.0,
  81. child: SizedBox(
  82. height: 20.0,
  83. width: 20.0,
  84. child: CupertinoActivityIndicator(),
  85. ),
  86. );
  87. } else {
  88. return Container();
  89. }
  90. },
  91. ),
  92. enablePullDown: false,
  93. enablePullUp: true,
  94. onLoading: controller.loadMoreHistory,
  95. onRefresh: controller.loadMoreHistory,
  96. child: ListView.builder(
  97. reverse: true,
  98. controller: controller.listScrollController,
  99. itemBuilder: _chatItemBuilder,
  100. itemCount: controller.chatItems.length));
  101. }),
  102. )),
  103. Container(
  104. margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
  105. width: 1.sw,
  106. decoration: BoxDecoration(
  107. color: Colors.white,
  108. borderRadius: BorderRadius.circular(24.w),
  109. boxShadow: const [
  110. BoxShadow(
  111. color: Color(0x4CDDDEE8),
  112. blurRadius: 10,
  113. offset: Offset(0, 4),
  114. spreadRadius: 0,
  115. )
  116. ]),
  117. child: Padding(
  118. padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
  119. child: Column(
  120. children: [
  121. Row(
  122. crossAxisAlignment: CrossAxisAlignment.end,
  123. children: [
  124. Expanded(
  125. child: Container(
  126. margin: EdgeInsets.only(right: 6.w),
  127. child: CupertinoTextField(
  128. controller: controller.inputController,
  129. padding: EdgeInsets.symmetric(vertical: 3.w),
  130. style: TextStyle(
  131. fontSize: 14.w, color: ColorName.primaryTextColor),
  132. placeholder: '有问题尽管问我~',
  133. placeholderStyle: TextStyle(
  134. fontSize: 14.w, color: const Color(0xFFAFAFAF)),
  135. textCapitalization: TextCapitalization.sentences,
  136. textInputAction: TextInputAction.newline,
  137. cursorColor: ColorName.colorPrimary,
  138. decoration: const BoxDecoration(),
  139. expands: true,
  140. maxLines: null,
  141. minLines: null,
  142. ),
  143. )),
  144. Image(
  145. image: Assets.images.iconChatAddFile.provider(),
  146. width: 26.w,
  147. height: 26.w),
  148. Container(
  149. margin: EdgeInsets.only(left: 16.w),
  150. child: GestureDetector(
  151. onTap: () {
  152. controller.sendMessage();
  153. },
  154. child: Image(
  155. image: Assets.images.iconChatSend.provider(),
  156. width: 26.w,
  157. height: 26.w),
  158. ),
  159. )
  160. ],
  161. )
  162. ],
  163. ),
  164. ),
  165. ),
  166. ],
  167. );
  168. }
  169. Widget _chatItemBuilder(BuildContext context, int index) {
  170. ChatItem chatItem = controller.chatItems[index];
  171. if (chatItem.role == 'user') {
  172. return _buildUserChatItem(context, chatItem);
  173. } else if (chatItem.role == 'assistant') {
  174. return _buildAssistantChatItem(context, chatItem);
  175. } else {
  176. return Container();
  177. }
  178. }
  179. Widget _buildAssistantChatItem(BuildContext context, ChatItem chatItem) {
  180. ProgressingChatItem? progressingChatItem;
  181. if (chatItem is ProgressingChatItem) {
  182. progressingChatItem = chatItem;
  183. }
  184. return Align(
  185. alignment: Alignment.centerLeft,
  186. child: IntrinsicWidth(
  187. child: progressingChatItem == null
  188. ? _buildAssistantChatItemContent(null, chatItem.content)
  189. : Obx(() {
  190. bool? isStreamStarted = progressingChatItem == null
  191. ? null
  192. : progressingChatItem.streamContent.isNotEmpty ||
  193. progressingChatItem.isFinished.value ||
  194. progressingChatItem.isFailed.value;
  195. return _buildAssistantChatItemContent(
  196. isStreamStarted,
  197. progressingChatItem!.isFailed.value
  198. ? progressingChatItem.error.value
  199. : progressingChatItem.streamContent.value);
  200. }),
  201. ),
  202. );
  203. }
  204. Container _buildAssistantChatItemContent(
  205. bool? isStreamStarted, String content) {
  206. return Container(
  207. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  208. margin: EdgeInsets.symmetric(vertical: 10.h),
  209. alignment: Alignment.centerLeft,
  210. constraints: BoxConstraints(
  211. maxWidth: 0.78.sw, // 65% of screen width
  212. ),
  213. decoration: BoxDecoration(
  214. border: isStreamStarted == null || isStreamStarted == true
  215. ? null
  216. : Border.all(color: ColorName.colorPrimary, width: 1.w),
  217. color: ColorName.white,
  218. borderRadius: BorderRadius.only(
  219. topRight: Radius.circular(20.w),
  220. bottomRight: Radius.circular(20.w),
  221. bottomLeft: Radius.circular(20.w))),
  222. child: isStreamStarted != null && isStreamStarted == false
  223. ? Row(
  224. children: [
  225. Image(
  226. image: Assets.images.iconStreamChatProgressing.provider(),
  227. width: 24.w,
  228. height: 20.w),
  229. Padding(
  230. padding: EdgeInsets.only(left: 8.w, right: 8.w),
  231. child: Text(
  232. "正在生成中,请稍后...",
  233. style: TextStyle(
  234. fontSize: 14.w, color: ColorName.tertiaryTextColor),
  235. ),
  236. ),
  237. ],
  238. )
  239. : SelectableText(content,
  240. style:
  241. TextStyle(fontSize: 14.w, color: ColorName.primaryTextColor)),
  242. );
  243. }
  244. Widget _buildUserChatItem(BuildContext context, ChatItem chatItem) {
  245. return Align(
  246. alignment: Alignment.centerRight,
  247. child: IntrinsicWidth(
  248. child: Container(
  249. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  250. margin: EdgeInsets.symmetric(vertical: 10.h),
  251. alignment: Alignment.centerRight,
  252. constraints: BoxConstraints(
  253. maxWidth: 0.78.sw, // 65% of screen width
  254. ),
  255. decoration: BoxDecoration(
  256. color: ColorName.colorPrimary,
  257. borderRadius: BorderRadius.only(
  258. topLeft: Radius.circular(16.w),
  259. bottomRight: Radius.circular(16.w),
  260. bottomLeft: Radius.circular(16.w))),
  261. child: Text(chatItem.content,
  262. style: TextStyle(fontSize: 14.w, color: ColorName.white)),
  263. ),
  264. ),
  265. );
  266. }
  267. Widget buildTopGradient() {
  268. return Container(
  269. width: 1.sw,
  270. height: 128.h,
  271. decoration: BoxDecoration(
  272. gradient: LinearGradient(
  273. colors: ['#E8EBFF'.toColor(), '#00E8EBFF'.toColor()],
  274. begin: Alignment.topCenter,
  275. end: Alignment.bottomCenter,
  276. stops: const [0.5, 1.0],
  277. ),
  278. ));
  279. }
  280. Widget buildBackgroundGradient() {
  281. return Container(
  282. width: 1.sw,
  283. height: 1.sh,
  284. decoration: BoxDecoration(
  285. gradient: LinearGradient(
  286. colors: ['#F2F8F4'.toColor(), '#F6F6F6'.toColor()],
  287. begin: Alignment.topCenter,
  288. end: Alignment.bottomCenter,
  289. stops: const [0, 1.0],
  290. ),
  291. ),
  292. );
  293. }
  294. }