From 8cb0688a88815705aecd9097b0c5373b5ad2f063 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 27 Jun 2025 16:21:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9askai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/deapsound_ai_service.dart | 50 ++----- .../meeting/views/ai_bottom_sheet.dart | 140 ++++++++---------- 2 files changed, 76 insertions(+), 114 deletions(-) diff --git a/lib/data/services/deapsound_ai_service.dart b/lib/data/services/deapsound_ai_service.dart index 4701bf674..da7181f4f 100644 --- a/lib/data/services/deapsound_ai_service.dart +++ b/lib/data/services/deapsound_ai_service.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import '../../core/utils/logger.dart'; import '../models/appconfig.dart'; import 'ai_service.dart'; @@ -60,27 +59,18 @@ class DeapsoundAIService implements AiService { String? userProperties, }) async* { final client = http.Client(); - final url = Uri.parse('https://api.deapsound.com/ai/funcchat'); - - List contextsList = []; - String msg = ''; - for (var i = 0; i < messages.length; i++) { - Map item = messages[i]; - if (i < messages.length - 1) { - if (item['role'] == 'user') { - contextsList.add(item['content']); - } - } else { - msg = item['content']; - } - } + final url = Uri.parse(baseUrl); final headers = { 'Content-Type': 'application/json', - 'Authorization': apiKey, + 'Authorization': 'Bearer $apiKey', }; - final requestBody = {"contexts": contextsList, "msg": msg}; + final requestBody = { + "model": model, + "messages": messages, + "stream": true, + }; try { final request = http.Request('POST', url) @@ -88,7 +78,6 @@ class DeapsoundAIService implements AiService { ..body = jsonEncode(requestBody); final response = await client.send(request); - //流式处理响应数据 await for (final chunk in response.stream .transform(utf8.decoder) .transform(const LineSplitter())) { @@ -96,28 +85,19 @@ class DeapsoundAIService implements AiService { if (chunk.startsWith('data:')) { final jsonStr = chunk.substring(5).trim(); + if (jsonStr == '[DONE]') { + break; + } - try { - final data = jsonDecode(jsonStr); - if (data['role'] == 'card') { - yield data['meta']; - if (data['meta']['musicaudio'] != null) { - break; - } - } - if (data['role'] == 'ai') { - String content = data['content'] ?? ''; - if (content.isNotEmpty) { - yield content; - } - } - } catch (e) { - Logger.error('JSON解析错误: ${e.toString()}'); + final data = jsonDecode(jsonStr); + final content = data['choices'][0]['delta']['content'] ?? ''; + if (content.isNotEmpty) { + yield content; } } } } catch (e) { - Logger.error('请求异常: ${e.toString()}'); + throw Exception('Unknown error'); } finally { client.close(); } diff --git a/lib/modules/meeting/views/ai_bottom_sheet.dart b/lib/modules/meeting/views/ai_bottom_sheet.dart index db5f964df..760274ed6 100644 --- a/lib/modules/meeting/views/ai_bottom_sheet.dart +++ b/lib/modules/meeting/views/ai_bottom_sheet.dart @@ -1,8 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import '../../../data/services/deapsound_ai_service.dart'; import '../controllers/meeting_details_controller.dart'; class AIBottomSheet extends StatefulWidget { @@ -14,6 +17,10 @@ class AIBottomSheet extends StatefulWidget { class _AIBottomSheetState extends State { final _controller = Get.find(); + final ScrollController _scrollController = ScrollController(); + + final _deapsoundAI = DeapsoundAIService(); + StreamSubscription? _aiResponseSubscription; final TextEditingController _texController = TextEditingController(); @@ -30,6 +37,8 @@ class _AIBottomSheetState extends State { @override void dispose() { _texController.dispose(); + _scrollController.dispose(); + _aiResponseSubscription?.cancel(); super.dispose(); } @@ -80,12 +89,13 @@ class _AIBottomSheetState extends State { ? Align( alignment: Alignment.topCenter, child: ListView.builder( + controller: _scrollController, itemCount: _dataList.length, shrinkWrap: true, reverse: true, itemBuilder: (BuildContext context, int index) { Map data = _dataList[index]; - return _item(data, index == 0); + return _item(data); }, ), ) @@ -97,47 +107,35 @@ class _AIBottomSheetState extends State { ); } - Widget _item(Map data, bool isBorder) { - return Container( - padding: EdgeInsets.symmetric(vertical: 20.w), - decoration: BoxDecoration( - border: isBorder - ? null - : Border( - bottom: BorderSide( - width: 1, - color: Colors.grey[400]!, - ), - ), - ), - child: Column( - children: [ - Align( - alignment: Alignment.centerRight, - child: Container( - padding: EdgeInsets.all(10.w), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(12.r), - ), - child: Text( - data['user'], - style: TextStyle( - fontSize: 14.sp, - ), + Widget _item(Map data) { + return Column( + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + data['user'], + style: TextStyle( + fontSize: 14.sp, ), ), ), - if (data['assistant'].isNotEmpty) - Container( - padding: EdgeInsets.symmetric(vertical: 20.w), - alignment: Alignment.centerLeft, - child: MarkdownBody( - data: data['assistant'], - ), + ), + if (data['assistant'].isNotEmpty) + Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + alignment: Alignment.centerLeft, + child: MarkdownBody( + data: data['assistant'], ), - ], - ), + ), + ], ); } @@ -256,11 +254,11 @@ class _AIBottomSheetState extends State { width: 26.w, height: 26.w, decoration: BoxDecoration( - color: Colors.grey, + color: _isLoading ? Colors.black87 : Colors.grey, borderRadius: BorderRadius.circular(26.r), ), child: Icon( - Icons.arrow_upward, + _isLoading ? Icons.pause : Icons.arrow_upward, size: 18.w, color: Colors.white, ), @@ -273,47 +271,31 @@ class _AIBottomSheetState extends State { void _sendMessage(String value) { if (_isLoading || value.isEmpty) return; - setState(() { - _isLoading = true; - }); - _dataList.insert(0, { - 'user': value, - 'assistant': ''' -# 角色 -你是一名卓越的会议记录专家,拥有超强的信息归纳与细分能力。不仅能高效处理各类通用场景会议,精准提炼会议核心内容,为后续思维导图生成奠定坚实基础,还能将整理好的会议记录巧妙转化为清晰直观的思维导图。 - -## 技能 -### 技能 1: 通用会议内容深度细分 -1. 深入全面剖析给定的通用场景会议内容,精准区分不同主题板块,如会议目标、讨论事项、决策结果、行动安排、问题与挑战等。 -2. 对每个主题板块进行细致分解,形成具体要点与细节。若涉及人员,人员名称保留[],清晰明确其角色与贡献;涉及流程,梳理出关键步骤与节点。 -3. 以层次分明、逻辑紧密的方式整理细分内容,使其契合思维导图的结构特点。 -===回复示例=== -- **会议目标**: - - 目标 1:[详细且全面阐述目标 1] - - 目标 2:[如有多个目标,依次详细罗列] -- **讨论事项**: - - 事项 1: - - 观点 1:[详细说明提出观点 1 的人员及具体内容] - - 观点 2:[详细阐述不同观点及相关人员] - - 事项 2:[按此格式详细罗列其他讨论事项] -- **决策结果**: - - 决策 1:[具体且完整表述决策内容] - - 决策 2:[详细列出其他决策] -- **行动安排**: - - 任务 1: - - 负责人:[具体姓名] - - 时间节点:[精确到具体时间] - - 任务内容:[详细描述任务具体内容] - - 任务 2:[依此格式详细罗列其他任务] -- **问题与挑战**: - - 问题 1:[具体清晰描述问题] - - 挑战 1:[详细说明面临的挑战及相关说明] -===示例结束=== -''', - }); _texController.clear(); setState(() { - _isLoading = false; + _isLoading = true; + _dataList.insert(0, { + 'user': value, + 'assistant': '', + }); }); + _scrollController.jumpTo(0); + final responseStream = _deapsoundAI.sendMessageStream( + messages: [ + {'role': 'user', 'content': value}, + ], + ); + _aiResponseSubscription = responseStream.listen( + (chunk) { + setState(() { + _dataList.first['assistant'] += chunk; + }); + }, + onDone: () { + setState(() { + _isLoading = false; + }); + }, + ); } }