From dc2bd04143496c4195a936ec44fd15931d36c068 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 22 Oct 2023 17:54:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ai=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/ai.png | Bin 0 -> 7926 bytes lib/http/api.dart | 9 + lib/http/video.dart | 21 ++ lib/models/video/ai.dart | 80 ++++++ .../video/detail/introduction/controller.dart | 23 ++ lib/pages/video/detail/introduction/view.dart | 151 ++++++----- lib/pages/video/detail/widgets/ai_detail.dart | 236 ++++++++++++++++++ lib/utils/utils.dart | 11 + 8 files changed, 472 insertions(+), 59 deletions(-) create mode 100644 assets/images/ai.png create mode 100644 lib/models/video/ai.dart create mode 100644 lib/pages/video/detail/widgets/ai_detail.dart diff --git a/assets/images/ai.png b/assets/images/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..19f11915cb9f429d720b44fdac48f2140099a31f GIT binary patch literal 7926 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 literal 0 HcmV?d00001 diff --git a/lib/http/api.dart b/lib/http/api.dart index 14f55319..f2f06007 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -327,4 +327,13 @@ class Api { // id=849312409672744983 // features=itemOpusStyle static const String dynamicDetail = '/x/polymer/web-dynamic/v1/detail'; + + // AI总结 + /// https://api.bilibili.com/x/web-interface/view/conclusion/get? + /// bvid=BV1ju4y1s7kn& + /// cid=1296086601& + /// up_mid=4641697& + /// w_rid=1607c6c5a4a35a1297e31992220900ae& + /// wts=1697033079 + static const String aiConclusion = '/x/web-interface/view/conclusion/get'; } diff --git a/lib/http/video.dart b/lib/http/video.dart index 5ca8a280..9429a04b 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -9,9 +9,11 @@ import 'package:pilipala/models/home/rcmd/result.dart'; import 'package:pilipala/models/model_hot_video_item.dart'; import 'package:pilipala/models/model_rec_video_item.dart'; import 'package:pilipala/models/user/fav_folder.dart'; +import 'package:pilipala/models/video/ai.dart'; import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/utils/storage.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; /// res.data['code'] == 0 请求正常返回结果 /// res.data['data'] 为结果 @@ -420,4 +422,23 @@ class VideoHttp { return {'status': true, 'data': res.data['data']}; } } + + static Future aiConclusion({ + String? bvid, + int? cid, + int? upMid, + }) async { + Map params = await WbiSign().makSign({ + 'bvid': bvid, + 'cid': cid, + 'up_mid': upMid, + }); + var res = await Request().get(Api.aiConclusion, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': AiConclusionModel.fromJson(res.data['data']), + }; + } + } } diff --git a/lib/models/video/ai.dart b/lib/models/video/ai.dart new file mode 100644 index 00000000..a06fa79d --- /dev/null +++ b/lib/models/video/ai.dart @@ -0,0 +1,80 @@ +class AiConclusionModel { + AiConclusionModel({ + this.code, + this.modelResult, + this.stid, + this.status, + this.likeNum, + this.dislikeNum, + }); + + int? code; + ModelResult? modelResult; + String? stid; + int? status; + int? likeNum; + int? dislikeNum; + + AiConclusionModel.fromJson(Map json) { + code = json['code']; + modelResult = ModelResult.fromJson(json['model_result']); + stid = json['stid']; + status = json['status']; + likeNum = json['like_num']; + dislikeNum = json['dislike_num']; + } +} + +class ModelResult { + ModelResult({ + this.resultType, + this.summary, + this.outline, + }); + + int? resultType; + String? summary; + List? outline; + + ModelResult.fromJson(Map json) { + resultType = json['result_type']; + summary = json['summary']; + outline = json['result_type'] == 2 + ? json['outline'] + .map((e) => OutlineItem.fromJson(e)) + .toList() + : []; + } +} + +class OutlineItem { + OutlineItem({ + this.title, + this.partOutline, + }); + + String? title; + List? partOutline; + + OutlineItem.fromJson(Map json) { + title = json['title']; + partOutline = json['part_outline'] + .map((e) => PartOutline.fromJson(e)) + .toList(); + } +} + +class PartOutline { + PartOutline({ + this.timestamp, + this.content, + }); + + int? timestamp; + String? content; + + PartOutline.fromJson(Map json) { + timestamp = json['timestamp']; + content = json['content']; + } +} diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 6c32dc33..5c959116 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -8,6 +8,7 @@ import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/user/fav_folder.dart'; +import 'package:pilipala/models/video/ai.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart'; @@ -62,6 +63,7 @@ class VideoIntroController extends GetxController { Timer? timer; bool isPaused = false; String heroTag = Get.arguments['heroTag']; + late ModelResult modelResult; @override void onInit() { @@ -561,4 +563,25 @@ class VideoIntroController extends GetxController { isScrollControlled: true, ); } + + // ai总结 + Future aiConclusion() async { + SmartDialog.showLoading(msg: '正在生产ai总结'); + var res = await VideoHttp.aiConclusion( + bvid: bvid, + cid: lastPlayCid.value, + upMid: videoDetail.value.owner!.mid!, + ); + if (res['status']) { + if (res['data'].modelResult.resultType == 0) { + SmartDialog.showToast('该视频不支持ai总结'); + } + if (res['data'].modelResult.resultType == 2 || + res['data'].modelResult.resultType == 1) { + modelResult = res['data'].modelResult; + } + } + SmartDialog.dismiss(); + return res; + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index ef8ab928..46d641a2 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -11,6 +11,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/introduction/controller.dart'; +import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; @@ -226,6 +227,17 @@ class _VideoInfoState extends State with TickerProviderStateMixin { arguments: {'face': face, 'heroTag': memberHeroTag}); } + // ai总结 + showAiBottomSheet() { + showBottomSheet( + context: context, + enableDrag: true, + builder: (BuildContext context) { + return AiDetail(modelResult: videoIntroController.modelResult); + }, + ); + } + @override Widget build(BuildContext context) { ThemeData t = Theme.of(context); @@ -238,70 +250,91 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => showIntroDetail(), - child: Padding( - padding: const EdgeInsets.only(bottom: 6), - child: Text( - !loadingStatus - ? widget.videoDetail!.title - : videoItem['title'], - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.bold, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - )), GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => showIntroDetail(), - child: Row( - children: [ - StatView( - theme: 'gray', - view: !widget.loadingStatus - ? widget.videoDetail!.stat!.view - : videoItem['stat'].view, - size: 'medium', - ), - const SizedBox(width: 10), - StatDanMu( - theme: 'gray', - danmu: !widget.loadingStatus - ? widget.videoDetail!.stat!.danmaku - : videoItem['stat'].danmaku, - size: 'medium', - ), - const SizedBox(width: 10), - Text( - Utils.dateFormat( - !widget.loadingStatus - ? widget.videoDetail!.pubdate - : videoItem['pubdate'], - formatType: 'detail'), - style: TextStyle( - fontSize: 12, - color: t.colorScheme.outline, - ), - ), - const SizedBox(width: 10), - if (videoIntroController.isShowOnlineTotal) - Obx( - () => Text( - '${videoIntroController.total.value}人在看', - style: TextStyle( - fontSize: 12, - color: t.colorScheme.outline, - ), - ), - ), - ], + child: Text( + !loadingStatus + ? widget.videoDetail!.title + : videoItem['title'], + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), ), - const SizedBox(height: 7), + Stack( + children: [ + GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => showIntroDetail(), + child: Padding( + padding: const EdgeInsets.only(top: 7, bottom: 6), + child: Row( + children: [ + StatView( + theme: 'gray', + view: !widget.loadingStatus + ? widget.videoDetail!.stat!.view + : videoItem['stat'].view, + size: 'medium', + ), + const SizedBox(width: 10), + StatDanMu( + theme: 'gray', + danmu: !widget.loadingStatus + ? widget.videoDetail!.stat!.danmaku + : videoItem['stat'].danmaku, + size: 'medium', + ), + const SizedBox(width: 10), + Text( + Utils.dateFormat( + !widget.loadingStatus + ? widget.videoDetail!.pubdate + : videoItem['pubdate'], + formatType: 'detail'), + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + const SizedBox(width: 10), + if (videoIntroController.isShowOnlineTotal) + Obx( + () => Text( + '${videoIntroController.total.value}人在看', + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + ), + ], + ), + ), + ), + Positioned( + right: 10, + top: 6, + child: GestureDetector( + onTap: () async { + var res = await videoIntroController.aiConclusion(); + if (res['status']) { + if (res['data'].modelResult.resultType == 2 || + res['data'].modelResult.resultType == 1) { + showAiBottomSheet(); + } + } + }, + child: + Image.asset('assets/images/ai.png', height: 22), + ), + ) + ], + ), // 点赞收藏转发 布局样式1 // SingleChildScrollView( // padding: const EdgeInsets.only(top: 7, bottom: 7), diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart new file mode 100644 index 00000000..fb280d91 --- /dev/null +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -0,0 +1,236 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/models/video/ai.dart'; +import 'package:pilipala/pages/video/detail/index.dart'; +import 'package:pilipala/utils/storage.dart'; +import 'package:pilipala/utils/utils.dart'; + +Box localCache = GStrorage.localCache; +late double sheetHeight; + +class AiDetail extends StatelessWidget { + final ModelResult? modelResult; + + const AiDetail({ + Key? key, + this.modelResult, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + sheetHeight = localCache.get('sheetHeight'); + return Container( + color: Theme.of(context).colorScheme.background, + padding: const EdgeInsets.only(left: 14, right: 14), + height: sheetHeight, + child: Column( + children: [ + InkWell( + onTap: () => Get.back(), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(3)), + ), + ), + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Text( + modelResult!.summary!, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + height: 1.5, + ), + ), + const SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, + itemCount: modelResult!.outline!.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Column( + children: [ + Text( + modelResult!.outline![index].title!, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + height: 1.5, + ), + ), + const SizedBox(height: 6), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: modelResult! + .outline![index].partOutline!.length, + itemBuilder: (context, i) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + children: [ + RichText( + text: TextSpan( + style: TextStyle( + fontSize: 13, + color: Theme.of(context) + .colorScheme + .onBackground, + height: 1.5, + ), + children: [ + TextSpan( + text: Utils.tampToSeektime( + modelResult! + .outline![index] + .partOutline![i] + .timestamp!), + style: TextStyle( + color: Theme.of(context) + .colorScheme + .primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 跳转到指定位置 + try { + Get.find( + tag: Get.arguments[ + 'heroTag']) + .plPlayerController + .seekTo( + Duration( + seconds: + Utils.duration( + Utils.tampToSeektime(modelResult! + .outline![ + index] + .partOutline![ + i] + .timestamp!) + .toString(), + ), + ), + ); + } catch (_) {} + }, + ), + const TextSpan(text: ' '), + TextSpan( + text: modelResult! + .outline![index] + .partOutline![i] + .content!), + ], + ), + ), + ], + ), + ], + ); + }, + ), + const SizedBox(height: 20), + ], + ); + }, + ) + ], + ), + ), + ), + ], + ), + ); + } + + InlineSpan buildContent(BuildContext context, content) { + List descV2 = content.descV2; + // type + // 1 普通文本 + // 2 @用户 + List spanChilds = List.generate(descV2.length, (index) { + final currentDesc = descV2[index]; + switch (currentDesc.type) { + case 1: + List spanChildren = []; + RegExp urlRegExp = RegExp(r'https?://\S+\b'); + Iterable matches = urlRegExp.allMatches(currentDesc.rawText); + + int previousEndIndex = 0; + for (Match match in matches) { + if (match.start > previousEndIndex) { + spanChildren.add(TextSpan( + text: currentDesc.rawText + .substring(previousEndIndex, match.start))); + } + spanChildren.add( + TextSpan( + text: match.group(0), + style: TextStyle( + color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色 + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + try { + Get.toNamed( + '/webview', + parameters: { + 'url': match.group(0)!, + 'type': 'url', + 'pageTitle': match.group(0)!, + }, + ); + } catch (err) { + SmartDialog.showToast(err.toString()); + } + }, + ), + ); + previousEndIndex = match.end; + } + + if (previousEndIndex < currentDesc.rawText.length) { + spanChildren.add(TextSpan( + text: currentDesc.rawText.substring(previousEndIndex))); + } + + TextSpan result = TextSpan(children: spanChildren); + return result; + case 2: + final colorSchemePrimary = Theme.of(context).colorScheme.primary; + final heroTag = Utils.makeHeroTag(currentDesc.bizId); + return TextSpan( + text: '@${currentDesc.rawText}', + style: TextStyle(color: colorSchemePrimary), + recognizer: TapGestureRecognizer() + ..onTap = () { + Get.toNamed( + '/member?mid=${currentDesc.bizId}', + arguments: {'face': '', 'heroTag': heroTag}, + ); + }, + ); + default: + return const TextSpan(); + } + }); + return TextSpan(children: spanChilds); + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index f571e10d..8982c178 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -286,4 +286,15 @@ class Utils { ); } } + + // 时间戳转时间 + static tampToSeektime(number) { + int hours = number ~/ 60; + int minutes = number % 60; + + String formattedHours = hours.toString().padLeft(2, '0'); + String formattedMinutes = minutes.toString().padLeft(2, '0'); + + return '$formattedHours:$formattedMinutes'; + } }