view.dart 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. import 'package:electronic_assistant/base/base_page.dart';
  2. import 'package:electronic_assistant/data/bean/agenda.dart';
  3. import 'package:electronic_assistant/data/bean/chat_item.dart';
  4. import 'package:electronic_assistant/data/bean/file_chat_item.dart';
  5. import 'package:electronic_assistant/data/bean/reference_chat_item.dart';
  6. import 'package:electronic_assistant/module/browser/view.dart';
  7. import 'package:electronic_assistant/module/chat/controller.dart';
  8. import 'package:electronic_assistant/resource/colors.gen.dart';
  9. import 'package:electronic_assistant/resource/string.gen.dart';
  10. import 'package:electronic_assistant/utils/expand.dart';
  11. import 'package:flutter/cupertino.dart';
  12. import 'package:flutter/material.dart';
  13. import 'package:flutter/services.dart';
  14. import 'package:flutter_screenutil/flutter_screenutil.dart';
  15. import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
  16. import 'package:get/get.dart';
  17. import 'package:lottie/lottie.dart';
  18. import 'package:markdown/markdown.dart' as md;
  19. import 'package:pull_to_refresh/pull_to_refresh.dart';
  20. import '../../data/bean/progressing_chat_item.dart';
  21. import '../../data/bean/talks.dart';
  22. import '../../resource/assets.gen.dart';
  23. import '../../router/app_pages.dart';
  24. enum ChatFromType {
  25. fromMain,
  26. fromTalkDetail,
  27. fromAnalysisBtn,
  28. fromTalkExample,
  29. fromMine,
  30. unknown
  31. }
  32. class ChatPage extends BasePage<ChatController> {
  33. const ChatPage({super.key});
  34. static start(ChatFromType fromType) {
  35. Get.toNamed(RoutePath.chat, arguments: [fromType]);
  36. }
  37. static startByTalk(ChatFromType fromType, TalkBean talkInfo,
  38. {Agenda? agenda}) {
  39. Get.toNamed(RoutePath.chat, arguments: [fromType, talkInfo, agenda]);
  40. }
  41. static startByTalkId(ChatFromType fromType, String talkId, {Agenda? agenda}) {
  42. Get.toNamed(RoutePath.chat, arguments: [fromType, talkId, agenda]);
  43. }
  44. @override
  45. bool immersive() {
  46. return true;
  47. }
  48. @override
  49. Color navigationBarColor() {
  50. return "#F6F6F6".color;
  51. }
  52. @override
  53. Widget buildBody(BuildContext context) {
  54. // 第一次启动时弹出定制窗口
  55. return Stack(
  56. children: [
  57. _buildBackgroundGradient(),
  58. _buildTopGradient(),
  59. Scaffold(
  60. backgroundColor: Colors.transparent,
  61. appBar: AppBar(
  62. leading: IconButton(
  63. icon: SizedBox(
  64. width: 24.w,
  65. height: 24.w,
  66. child: Assets.images.iconBack.image()),
  67. onPressed: () {
  68. Get.back();
  69. },
  70. ),
  71. scrolledUnderElevation: 0,
  72. backgroundColor: Colors.transparent,
  73. systemOverlayStyle: SystemUiOverlayStyle.dark,
  74. centerTitle: true,
  75. title: IntrinsicWidth(
  76. child: Row(
  77. mainAxisAlignment: MainAxisAlignment.center,
  78. children: [
  79. Image(
  80. image: Assets.images.iconChatXiaoTin.provider(),
  81. width: 28.w,
  82. height: 28.w),
  83. Container(
  84. margin: EdgeInsets.only(left: 6.w),
  85. child: Text('聊天',
  86. style: TextStyle(
  87. fontSize: 16.w,
  88. fontWeight: FontWeight.bold,
  89. color: ColorName.primaryTextColor))),
  90. ],
  91. ),
  92. ),
  93. ),
  94. body: buildBodyContent(context),
  95. )
  96. ],
  97. );
  98. }
  99. Widget buildBodyContent(BuildContext context) {
  100. return Column(
  101. children: [
  102. Expanded(
  103. child: Container(
  104. padding: EdgeInsets.symmetric(horizontal: 12.w),
  105. child: Obx(() {
  106. return NotificationListener<ScrollNotification>(
  107. onNotification: (scrollNotification) {
  108. if (scrollNotification is ScrollStartNotification) {
  109. FocusScope.of(context).unfocus();
  110. }
  111. return false;
  112. },
  113. child: SmartRefresher(
  114. controller: controller.refreshController,
  115. footer: CustomFooter(
  116. loadStyle: LoadStyle.ShowWhenLoading,
  117. builder: (context, mode) {
  118. if (mode == LoadStatus.loading ||
  119. mode == LoadStatus.canLoading) {
  120. return const SizedBox(
  121. height: 60.0,
  122. child: SizedBox(
  123. height: 20.0,
  124. width: 20.0,
  125. child: CupertinoActivityIndicator(),
  126. ),
  127. );
  128. } else {
  129. return Container();
  130. }
  131. },
  132. ),
  133. enablePullDown: false,
  134. enablePullUp: true,
  135. onLoading: controller.loadMoreHistory,
  136. onRefresh: controller.loadMoreHistory,
  137. child: ListView.builder(
  138. reverse: true,
  139. controller: controller.listScrollController,
  140. itemBuilder: _chatItemBuilder,
  141. itemCount: controller.chatItems.length),
  142. ),
  143. );
  144. }),
  145. )),
  146. Container(
  147. margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
  148. width: 1.sw,
  149. decoration: BoxDecoration(
  150. color: Colors.white,
  151. borderRadius: BorderRadius.circular(12.w),
  152. boxShadow: const [
  153. BoxShadow(
  154. color: Color(0x4CDDDEE8),
  155. blurRadius: 10,
  156. offset: Offset(0, 4),
  157. spreadRadius: 0,
  158. )
  159. ]),
  160. child: Padding(
  161. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
  162. child: Column(
  163. children: [
  164. Obx(() {
  165. TalkBean? talkInfo = controller.talkInfo.value;
  166. if (talkInfo == null) {
  167. return Container();
  168. } else {
  169. return _buildReferenceFile(talkInfo);
  170. }
  171. }),
  172. Row(
  173. crossAxisAlignment: CrossAxisAlignment.end,
  174. children: [
  175. Expanded(
  176. child: Container(
  177. margin: EdgeInsets.only(right: 6.w),
  178. child: CupertinoTextField(
  179. controller: controller.inputController,
  180. padding: EdgeInsets.symmetric(vertical: 3.w),
  181. style: TextStyle(
  182. fontSize: 14.w, color: ColorName.primaryTextColor),
  183. placeholder: '有问题尽管问我~',
  184. placeholderStyle: TextStyle(
  185. fontSize: 14.w, color: const Color(0xFFAFAFAF)),
  186. textCapitalization: TextCapitalization.sentences,
  187. textInputAction: TextInputAction.newline,
  188. cursorColor: ColorName.colorPrimary,
  189. decoration: const BoxDecoration(),
  190. expands: false,
  191. minLines: 1,
  192. maxLines: 6,
  193. ),
  194. )),
  195. GestureDetector(
  196. onTap: () {
  197. controller.onAddFileClick();
  198. },
  199. child: Image(
  200. image: Assets.images.iconChatAddFile.provider(),
  201. width: 26.w,
  202. height: 26.w),
  203. ),
  204. Container(
  205. margin: EdgeInsets.only(left: 16.w),
  206. child: GestureDetector(
  207. onTap: () {
  208. controller.onSendClick();
  209. },
  210. child: Image(
  211. image: Assets.images.iconChatSend.provider(),
  212. width: 26.w,
  213. height: 26.w),
  214. ),
  215. )
  216. ],
  217. )
  218. ],
  219. ),
  220. ),
  221. ),
  222. ],
  223. );
  224. }
  225. Widget _chatItemBuilder(BuildContext context, int index) {
  226. ChatItem chatItem = controller.chatItems[index];
  227. if (chatItem.role == 'user') {
  228. return _buildUserChatItem(context, chatItem);
  229. } else if (chatItem.role == 'assistant') {
  230. return _buildAssistantChatItem(context, chatItem);
  231. } else {
  232. return Container();
  233. }
  234. }
  235. Widget _buildAssistantChatItem(BuildContext context, ChatItem chatItem) {
  236. ProgressingChatItem? progressingChatItem;
  237. if (chatItem is ProgressingChatItem) {
  238. progressingChatItem = chatItem;
  239. }
  240. return Align(
  241. alignment: Alignment.centerLeft,
  242. child: IntrinsicWidth(
  243. child: progressingChatItem == null
  244. ? _buildAssistantChatItemContent(
  245. null, chatItem.content, chatItem.id)
  246. : Obx(() {
  247. bool? isStreamStarted = progressingChatItem == null
  248. ? null
  249. : progressingChatItem.streamContent.isNotEmpty ||
  250. progressingChatItem.isFinished.value ||
  251. progressingChatItem.isFailed.value;
  252. return _buildAssistantChatItemContent(
  253. isStreamStarted,
  254. progressingChatItem!.isFailed.value
  255. ? progressingChatItem.error.value
  256. : progressingChatItem.streamContent.value,
  257. chatItem.id);
  258. }),
  259. ),
  260. );
  261. }
  262. Widget _buildAssistantChatItemContent(
  263. bool? isStreamStarted, String content, String id) {
  264. return Column(
  265. crossAxisAlignment: CrossAxisAlignment.start,
  266. children: [
  267. SizedBox(height: 10.h),
  268. isStreamStarted != null && isStreamStarted == false
  269. ? Container(
  270. padding: const EdgeInsets.all(1),
  271. decoration: BoxDecoration(
  272. color: ColorName.colorPrimary,
  273. gradient: LinearGradient(
  274. colors: ['#B57AFF'.toColor(), '#4466FF'.toColor()],
  275. stops: const [0, 1.0],
  276. begin: Alignment.topLeft,
  277. end: Alignment.bottomRight,
  278. ),
  279. borderRadius: BorderRadius.only(
  280. topRight: Radius.circular(20.w),
  281. bottomRight: Radius.circular(20.w),
  282. bottomLeft: Radius.circular(20.w))),
  283. child: Container(
  284. decoration: BoxDecoration(
  285. color: ColorName.white,
  286. borderRadius: BorderRadius.only(
  287. topRight: Radius.circular(20.w),
  288. bottomRight: Radius.circular(20.w),
  289. bottomLeft: Radius.circular(20.w))),
  290. padding:
  291. EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  292. child: Lottie.asset(
  293. "assets/anim/anim_chat_response_loading.zip",
  294. width: 46.w,
  295. height: 20.w)),
  296. )
  297. : Container(
  298. decoration: BoxDecoration(
  299. color: ColorName.white,
  300. border: Border.all(color: '#ECECEC'.color, width: 1.w),
  301. borderRadius: BorderRadius.only(
  302. topRight: Radius.circular(20.w),
  303. bottomRight: Radius.circular(20.w),
  304. bottomLeft: Radius.circular(20.w))),
  305. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  306. alignment: Alignment.centerLeft,
  307. constraints: BoxConstraints(
  308. maxWidth: 0.78.sw,
  309. ),
  310. child: SelectionArea(
  311. child: HtmlWidget(
  312. onTapUrl: (url) {
  313. BrowserPage.start(url);
  314. return true;
  315. },
  316. md.markdownToHtml(content, inlineSyntaxes: [
  317. md.InlineHtmlSyntax(),
  318. md.StrikethroughSyntax(),
  319. md.EmojiSyntax(),
  320. md.ColorSwatchSyntax(),
  321. md.AutolinkExtensionSyntax(),
  322. md.ImageSyntax()
  323. ], blockSyntaxes: [
  324. const md.FencedCodeBlockSyntax(),
  325. const md.HeaderWithIdSyntax(),
  326. const md.SetextHeaderWithIdSyntax(),
  327. const md.UnorderedListWithCheckboxSyntax(),
  328. const md.OrderedListWithCheckboxSyntax(),
  329. const md.FootnoteDefSyntax(),
  330. const md.AlertBlockSyntax(),
  331. ]),
  332. textStyle: TextStyle(
  333. fontSize: 14.w, color: ColorName.primaryTextColor),
  334. ),
  335. ),
  336. ),
  337. // Container(
  338. // padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  339. // alignment: Alignment.centerLeft,
  340. // constraints: BoxConstraints(
  341. // maxWidth: 0.78.sw,
  342. // ),
  343. // decoration: BoxDecoration(
  344. // border: isStreamStarted == null || isStreamStarted == true
  345. // ? null
  346. // : Border.all(color: ColorName.colorPrimary, width: 1.w),
  347. // color: ColorName.white,
  348. // borderRadius: BorderRadius.only(
  349. // topRight: Radius.circular(20.w),
  350. // bottomRight: Radius.circular(20.w),
  351. // bottomLeft: Radius.circular(20.w))),
  352. // child: isStreamStarted != null && isStreamStarted == false
  353. // ? Lottie.asset("assets/anim/anim_chat_response_loading.zip",
  354. // width: 46.w, height: 20.w)
  355. // : SelectionArea(
  356. // child: HtmlWidget(
  357. // onTapUrl: (url) {
  358. // BrowserPage.start(url);
  359. // return true;
  360. // },
  361. // md.markdownToHtml(content, inlineSyntaxes: [
  362. // md.InlineHtmlSyntax(),
  363. // md.StrikethroughSyntax(),
  364. // md.EmojiSyntax(),
  365. // md.ColorSwatchSyntax(),
  366. // md.AutolinkExtensionSyntax(),
  367. // md.ImageSyntax()
  368. // ], blockSyntaxes: [
  369. // const md.FencedCodeBlockSyntax(),
  370. // const md.HeaderWithIdSyntax(),
  371. // const md.SetextHeaderWithIdSyntax(),
  372. // const md.UnorderedListWithCheckboxSyntax(),
  373. // const md.OrderedListWithCheckboxSyntax(),
  374. // const md.FootnoteDefSyntax(),
  375. // const md.AlertBlockSyntax(),
  376. // ]),
  377. // textStyle: TextStyle(
  378. // fontSize: 14.w, color: ColorName.primaryTextColor),
  379. // ),
  380. // )),
  381. Obx(() {
  382. return Visibility(
  383. visible: id == controller.chatAiTagId.value,
  384. child: Container(
  385. margin: EdgeInsets.only(top: 6.h),
  386. padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 3.w),
  387. decoration: BoxDecoration(
  388. borderRadius: BorderRadius.all(Radius.circular(12.w)),
  389. color: "#EFEEF1".color),
  390. child: Text(
  391. StringName.chatItemAiTag.tr,
  392. style: TextStyle(
  393. height: 1,
  394. fontSize: 10.sp,
  395. color: ColorName.tertiaryTextColor),
  396. ),
  397. ),
  398. );
  399. }),
  400. SizedBox(height: 10.h)
  401. ],
  402. );
  403. }
  404. Widget _buildUserChatItem(BuildContext context, ChatItem chatItem) {
  405. if (chatItem is FileChatItem) {
  406. return _buildUserFileChatItem(context, chatItem);
  407. } else if (chatItem is ReferenceChatItem) {
  408. return _buildUserNormalChatItem(context, chatItem,
  409. referenceTalkTitle: chatItem.talkInfo.title.value);
  410. }
  411. return _buildUserNormalChatItem(context, chatItem,
  412. referenceTalkTitle: chatItem.talkTitle);
  413. }
  414. Widget _buildUserNormalChatItem(BuildContext context, ChatItem chatItem,
  415. {String? referenceTalkTitle}) {
  416. return Align(
  417. alignment: Alignment.centerRight,
  418. child: Column(
  419. crossAxisAlignment: CrossAxisAlignment.end,
  420. children: [
  421. IntrinsicWidth(
  422. child: Container(
  423. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  424. margin: referenceTalkTitle == null
  425. ? EdgeInsets.symmetric(vertical: 10.h)
  426. : EdgeInsets.only(top: 10.h),
  427. alignment: Alignment.centerRight,
  428. constraints: BoxConstraints(
  429. maxWidth: 0.78.sw,
  430. ),
  431. decoration: BoxDecoration(
  432. color: ColorName.colorPrimary,
  433. borderRadius: BorderRadius.only(
  434. topLeft: Radius.circular(16.w),
  435. bottomRight: Radius.circular(16.w),
  436. bottomLeft: Radius.circular(16.w))),
  437. child: SelectableText(chatItem.content,
  438. style: TextStyle(fontSize: 14.w, color: ColorName.white)),
  439. ),
  440. ),
  441. if (referenceTalkTitle != null)
  442. Container(
  443. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
  444. margin: EdgeInsets.only(top: 8.h, bottom: 10.h),
  445. constraints: BoxConstraints(
  446. maxWidth: 0.78.sw,
  447. ),
  448. decoration: BoxDecoration(
  449. color: "#EFEFEF".color,
  450. borderRadius: BorderRadius.all(Radius.circular(10.w))),
  451. child: Row(
  452. children: [
  453. Image(
  454. image: Assets.images.iconReferenceChatArrow.provider(),
  455. width: 16.w,
  456. height: 16.w),
  457. Container(
  458. margin: EdgeInsets.only(right: 2.w, left: 4.w),
  459. child: Image(
  460. image: Assets.images.iconReferenceChatFile.provider(),
  461. width: 16.w,
  462. height: 16.w),
  463. ),
  464. Text(referenceTalkTitle,
  465. style: TextStyle(
  466. fontSize: 12.w,
  467. color: ColorName.secondaryTextColor,
  468. overflow: TextOverflow.ellipsis)),
  469. ],
  470. ),
  471. ),
  472. ],
  473. ),
  474. );
  475. }
  476. Widget _buildUserFileChatItem(BuildContext context, FileChatItem chatItem) {
  477. return Align(
  478. alignment: Alignment.centerRight,
  479. child: Container(
  480. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 16.h),
  481. margin: EdgeInsets.symmetric(vertical: 10.h),
  482. constraints: BoxConstraints(
  483. maxWidth: 0.56.sw,
  484. ),
  485. decoration: BoxDecoration(
  486. color: ColorName.white,
  487. borderRadius: BorderRadius.only(
  488. topLeft: Radius.circular(16.w),
  489. bottomRight: Radius.circular(16.w),
  490. bottomLeft: Radius.circular(16.w)),
  491. border: Border.all(color: "#ECECEC".color, width: 1.w),
  492. ),
  493. child: Row(
  494. crossAxisAlignment: CrossAxisAlignment.center,
  495. children: [
  496. Container(
  497. margin: EdgeInsets.only(right: 6.w),
  498. child: Image(
  499. image: Assets.images.iconFilesFile.provider(),
  500. width: 30.w,
  501. height: 32.w),
  502. ),
  503. Flexible(
  504. child: Column(
  505. crossAxisAlignment: CrossAxisAlignment.start,
  506. children: [
  507. Text(chatItem.talkInfo.title.value.orEmpty,
  508. maxLines: 1,
  509. style: TextStyle(
  510. fontSize: 14.w,
  511. color: ColorName.primaryTextColor,
  512. fontWeight: FontWeight.bold,
  513. overflow: TextOverflow.ellipsis)),
  514. Text(chatItem.talkInfo.summary.value.orEmpty,
  515. maxLines: 1,
  516. style: TextStyle(
  517. fontSize: 12.w,
  518. color: ColorName.secondaryTextColor,
  519. overflow: TextOverflow.ellipsis)),
  520. ],
  521. ),
  522. ),
  523. ],
  524. ),
  525. ),
  526. );
  527. }
  528. _buildReferenceFile(TalkBean talkInfo) {
  529. if (talkInfo.oversizeFile == true) {
  530. return _buildOverSizeReference(talkInfo);
  531. } else {
  532. return _buildNormalReference(talkInfo);
  533. }
  534. }
  535. Container _buildOverSizeReference(TalkBean talkInfo) {
  536. return Container(
  537. margin: EdgeInsets.only(bottom: 14.h),
  538. padding: EdgeInsets.only(left: 8.w, top: 8.h, right: 10.w, bottom: 8.h),
  539. decoration: BoxDecoration(
  540. borderRadius: BorderRadius.all(Radius.circular(8.w)),
  541. border: Border.all(color: "#F0F0F0".color, width: 1.w),
  542. ),
  543. child: Column(
  544. children: [
  545. Row(
  546. children: [
  547. Text(talkInfo.title.value.orEmpty,
  548. style: TextStyle(
  549. fontWeight: FontWeight.bold,
  550. fontSize: 14.w,
  551. color: ColorName.primaryTextColor)),
  552. const Spacer(),
  553. GestureDetector(
  554. onTap: () => controller.onDeleteReference(),
  555. child: Container(
  556. margin: EdgeInsets.only(left: 8.w),
  557. child: Image(
  558. image:
  559. Assets.images.iconReferenceChatDeleteFile.provider(),
  560. width: 18.w,
  561. height: 18.w),
  562. ),
  563. ),
  564. ],
  565. ),
  566. Container(
  567. margin: EdgeInsets.only(top: 11.h),
  568. child: Row(
  569. children: [
  570. Container(
  571. margin: EdgeInsets.only(right: 2.w),
  572. child: Image(
  573. image: Assets.images.iconReferenceChatFile.provider(),
  574. width: 16.w,
  575. height: 16.w),
  576. ),
  577. Text("谈话·超长内容",
  578. style: TextStyle(
  579. fontSize: 12.w, color: ColorName.tertiaryTextColor)),
  580. ],
  581. ),
  582. )
  583. ],
  584. ),
  585. );
  586. }
  587. _buildNormalReference(TalkBean talkInfo) {
  588. return Container(
  589. decoration: BoxDecoration(
  590. color: "#F6F6F6".color,
  591. borderRadius: BorderRadius.all(Radius.circular(6.w)),
  592. ),
  593. padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 7.h),
  594. margin: EdgeInsets.only(bottom: 14.h),
  595. child: Row(
  596. children: [
  597. Image(
  598. image: Assets.images.iconReferenceChatArrow.provider(),
  599. width: 16.w,
  600. height: 16.w),
  601. Container(
  602. margin: EdgeInsets.only(right: 2.w, left: 4.w),
  603. child: Image(
  604. image: Assets.images.iconReferenceChatFile.provider(),
  605. width: 16.w,
  606. height: 16.w),
  607. ),
  608. Text(talkInfo.title.value.orEmpty,
  609. overflow: TextOverflow.ellipsis,
  610. maxLines: 1,
  611. style: TextStyle(
  612. fontSize: 12.w,
  613. color: ColorName.primaryTextColor,
  614. overflow: TextOverflow.ellipsis)),
  615. const Spacer(),
  616. Container(
  617. margin: EdgeInsets.only(left: 8.w),
  618. child: GestureDetector(
  619. onTap: () => controller.onDeleteReference(),
  620. child: Image(
  621. image: Assets.images.iconReferenceChatDeleteFile.provider(),
  622. width: 18.w,
  623. height: 18.w),
  624. ),
  625. ),
  626. ],
  627. ),
  628. );
  629. }
  630. Widget _buildTopGradient() {
  631. return Container(
  632. width: 1.sw,
  633. height: 128.h,
  634. decoration: BoxDecoration(
  635. gradient: LinearGradient(
  636. colors: ['#E8EBFF'.toColor(), '#00E8EBFF'.toColor()],
  637. begin: Alignment.topCenter,
  638. end: Alignment.bottomCenter,
  639. stops: const [0.5, 1.0],
  640. ),
  641. ));
  642. }
  643. Widget _buildBackgroundGradient() {
  644. return Container(
  645. width: 1.sw,
  646. height: 1.sh,
  647. decoration: BoxDecoration(
  648. gradient: LinearGradient(
  649. colors: ['#F2F8F4'.toColor(), '#F6F6F6'.toColor()],
  650. begin: Alignment.topCenter,
  651. end: Alignment.bottomCenter,
  652. stops: const [0, 1.0],
  653. ),
  654. ),
  655. );
  656. }
  657. }