From 38dcaa6066e2e4d28b527542d3955953a6682c73 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 30 Jan 2025 14:58:06 +0800 Subject: [PATCH] :sparkles: AI Post Insight --- assets/translations/en-US.json | 6 +- assets/translations/zh-CN.json | 6 +- assets/translations/zh-HK.json | 6 +- assets/translations/zh-TW.json | 6 +- lib/screens/wallet.dart | 3 - lib/widgets/post/post_item.dart | 112 ++++++++++++++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 34 +++--- pubspec.yaml | 1 + 9 files changed, 152 insertions(+), 24 deletions(-) diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 48e5f50..aecc9a6 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -554,6 +554,9 @@ "postImageShareAds": "Explore posts on the Solar Network", "postShare": "Share", "postShareImage": "Share via Image", + "postGetInsight": "Get Insight", + "postGetInsightTitle": "AI Insight", + "postGetInsightDescription": "AI may make mistakes, check important information.", "appInitializing": "Initializing", "poweredBy": "Powered by {}", "shareIntent": "Share", @@ -600,5 +603,6 @@ "walletCurrency": { "one": "{} Source Point", "other": "{} Source Points" - } + }, + "aiThinkingProcess": "AI Thinking Process" } diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index d2f6de8..228299e 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -552,6 +552,9 @@ "postImageShareAds": "来 Solar Network 探索更多有趣帖子", "postShare": "分享", "postShareImage": "分享帖图", + "postGetInsight": "获取见解", + "postGetInsightTitle": "AI 见解", + "postGetInsightDescription": "AI 可能会出错,检查信息真实性。", "appInitializing": "正在初始化", "poweredBy": "由 {} 提供支持", "shareIntent": "分享", @@ -598,5 +601,6 @@ "walletCurrency": { "one": "{} 源点", "other": "{} 源点" - } + }, + "aiThinkingProcess": "AI 思考过程" } diff --git a/assets/translations/zh-HK.json b/assets/translations/zh-HK.json index 8ffad9b..a8724fd 100644 --- a/assets/translations/zh-HK.json +++ b/assets/translations/zh-HK.json @@ -552,6 +552,9 @@ "postImageShareAds": "來 Solar Network 探索更多有趣帖子", "postShare": "分享", "postShareImage": "分享帖圖", + "postGetInsight": "獲取見解", + "postGetInsightTitle": "AI 見解", + "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", "appInitializing": "正在初始化", "poweredBy": "由 {} 提供支持", "shareIntent": "分享", @@ -598,5 +601,6 @@ "walletCurrency": { "one": "{} 源點", "other": "{} 源點" - } + }, + "aiThinkingProcess": "AI 思考過程" } diff --git a/assets/translations/zh-TW.json b/assets/translations/zh-TW.json index c37cd8c..9c238b2 100644 --- a/assets/translations/zh-TW.json +++ b/assets/translations/zh-TW.json @@ -552,6 +552,9 @@ "postImageShareAds": "來 Solar Network 探索更多有趣帖子", "postShare": "分享", "postShareImage": "分享帖圖", + "postGetInsight": "獲取見解", + "postGetInsightTitle": "AI 見解", + "postGetInsightDescription": "AI 可能會出錯,檢查信息真實性。", "appInitializing": "正在初始化", "poweredBy": "由 {} 提供支持", "shareIntent": "分享", @@ -598,5 +601,6 @@ "walletCurrency": { "one": "{} 源點", "other": "{} 源點" - } + }, + "aiThinkingProcess": "AI 思考過程" } diff --git a/lib/screens/wallet.dart b/lib/screens/wallet.dart index 4aa8d58..a4f6150 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 7295d98..1b08e3d 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -1,6 +1,8 @@ +import 'dart:developer'; import 'dart:io'; import 'dart:math' as math; +import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; @@ -34,6 +36,7 @@ import 'package:surface/widgets/post/post_meta_editor.dart'; import 'package:surface/widgets/post/post_reaction.dart'; import 'package:surface/widgets/post/publisher_popover.dart'; import 'package:surface/widgets/universal_image.dart'; +import 'package:xml/xml.dart'; class PostItem extends StatelessWidget { final SnPost data; @@ -817,6 +820,22 @@ class _PostContentHeader extends StatelessWidget { }, ), const PopupMenuDivider(), + PopupMenuItem( + child: Row( + children: [ + const Icon(Symbols.book_4_spark), + const Gap(16), + Text('postGetInsight').tr(), + ], + ), + onTap: () { + showModalBottomSheet( + context: context, + builder: (context) => _PostGetInsightSheet(postId: data.id), + ); + }, + ), + const PopupMenuDivider(), PopupMenuItem( onTap: onShare, child: Row( @@ -1181,3 +1200,96 @@ class _PostAbuseReportDialogState extends State<_PostAbuseReportDialog> { ); } } + +class _PostGetInsightSheet extends StatefulWidget { + final int postId; + + const _PostGetInsightSheet({required this.postId}); + + @override + State<_PostGetInsightSheet> createState() => _PostGetInsightSheetState(); +} + +class _PostGetInsightSheetState extends State<_PostGetInsightSheet> { + String? _response; + String? _thinkingProcess; + + Future _fetchResponse() async { + try { + final sn = context.read(); + final resp = await sn.client.get('/cgi/co/posts/${widget.postId}/insight', + options: Options( + sendTimeout: const Duration(minutes: 10), + receiveTimeout: const Duration(minutes: 10), + )); + final out = resp.data['response'] as String; + final document = XmlDocument.parse(out); + _thinkingProcess = document.getElement('think')?.innerText.trim(); + RegExp cleanThinkingRegExp = RegExp(r'[\s\S]*?'); + setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim()); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } + } + + @override + void initState() { + super.initState(); + _fetchResponse(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Symbols.book_4_spark, size: 24), + const Gap(16), + Text('postGetInsightTitle', style: Theme.of(context).textTheme.titleLarge).tr(), + ], + ).padding(horizontal: 20, top: 16, bottom: 12), + const Gap(4), + Text('postGetInsightDescription', style: Theme.of(context).textTheme.bodySmall).tr().padding(horizontal: 20), + const Gap(4), + if (_response == null) + Expanded( + child: Center( + child: CircularProgressIndicator(), + ), + ) + else + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + if (_thinkingProcess != null && _thinkingProcess!.isNotEmpty) + ExpansionTile( + leading: const Icon(Symbols.info), + title: Text('aiThinkingProcess'.tr()), + tilePadding: const EdgeInsets.symmetric(horizontal: 20), + collapsedBackgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, + minTileHeight: 32, + children: [ + SelectableText( + _thinkingProcess!, + style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontStyle: FontStyle.italic), + ).padding(horizontal: 20, vertical: 8), + ], + ).padding(vertical: 8), + SelectionArea( + child: MarkdownTextContent( + content: _response!, + ), + ).padding(horizontal: 20, top: 8), + ], + ), + ), + ), + ], + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d187442..e55412b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import bitsdojo_window_macos import connectivity_plus import device_info_plus +import file_picker import file_saver import file_selector_macos import firebase_analytics @@ -36,6 +37,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 3eaac7b..b35d8fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -362,10 +362,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 + sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a url: "https://pub.dev" source: hosted - version: "11.2.1" + version: "11.2.2" device_info_plus_platform_interface: dependency: transitive description: @@ -378,10 +378,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_smart_retry: dependency: "direct main" description: @@ -394,10 +394,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" dismissible_page: dependency: "direct main" description: @@ -490,10 +490,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204 + sha256: c9943dd7d702ab4199d199bc151a2d79c86db031a02ad84566dab58c494d2adc url: "https://pub.dev" source: hosted - version: "8.1.7" + version: "8.3.1" file_saver: dependency: "direct main" description: @@ -886,10 +886,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: daf3ff5570f55396b2d2c9bf8136d7db3a8acf208ac0cef92a3ae2beb9a81550 + sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" url: "https://pub.dev" source: hosted - version: "14.7.1" + version: "14.7.2" google_fonts: dependency: "direct main" description: @@ -1334,10 +1334,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" + sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 url: "https://pub.dev" source: hosted - version: "8.1.3" + version: "8.1.4" package_info_plus_platform_interface: dependency: transitive description: @@ -1710,10 +1710,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e + sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.1" shared_preferences_android: dependency: transitive description: @@ -2059,10 +2059,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" url: "https://pub.dev" source: hosted - version: "1.1.15" + version: "1.1.16" vector_graphics_codec: dependency: transitive description: @@ -2216,7 +2216,7 @@ packages: source: hosted version: "1.1.0" xml: - dependency: transitive + dependency: "direct main" description: name: xml sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 diff --git a/pubspec.yaml b/pubspec.yaml index 21fe8cf..5f32d6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -117,6 +117,7 @@ dependencies: cached_network_image: ^3.4.1 flutter_inappwebview: ^6.1.5 html: ^0.15.5 + xml: ^6.5.0 dev_dependencies: flutter_test: