Browse Source

Merge commit '78a9b0f666c34de7eafe2ae25cce277b3c68f6da' into lxm

newdev_shunjiawei
lxm 2 years ago
parent
commit
10ee921bb6
  1. 30
      lib/data/providers/agent_provider.dart
  2. 2
      lib/data/services/ai_service.dart
  3. 9
      lib/data/services/deapsound_ai_service.dart
  4. 1
      lib/main.dart
  5. 7
      lib/modules/chat/bindings/chat_binding.dart
  6. 59
      lib/modules/chat/controllers/chat_controller.dart
  7. 6
      lib/modules/chat/models/message_model.dart
  8. 107
      lib/modules/chat/views/chat_music_card.dart
  9. 179
      lib/modules/chat/views/chat_navigation_card.dart
  10. 50
      lib/modules/chat/views/chat_view.dart
  11. 94
      lib/modules/chat/views/weather_day_card.dart
  12. 15
      lib/modules/meeting/controllers/meeting_record_controller.dart

30
lib/data/providers/agent_provider.dart

@ -1,18 +1,18 @@
import '../models/agent.dart'; import '../models/agent.dart';
import 'package:get/get.dart';
class AgentProvider { class AgentProvider {
static final List<Agent> predefinedAgents = [ static final List<Agent> predefinedAgents = [
Agent(// 个人助理 Emma const Agent(
// 个人助理 Emma
id: 'personal_assistant', id: 'personal_assistant',
name: 'personalAssistantName', name: 'personalAssistantName',
avatarUrl: 'assets/images/agents/assistant.png', avatarUrl: 'assets/images/agents/assistant.png',
description: description: 'personalAssistantDescription',
'personalAssistantDescription',
tag: 'assistantTag', // '助理' tag: 'assistantTag', // '助理'
isOnline: true, isOnline: true,
voice: "zh-CN-XiaoxiaoNeural", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: 'personalAssistantWelcome',// "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?", welcomeMessage:
'personalAssistantWelcome', // "你好!我是 Emma,很高兴成为你的私人助理。我可以帮你处理各种任务,比如时间管理、目标规划、信息整理等。有什么我可以帮你的吗?",
voiceWelcomeMessage: "personalAssistantVoiceWelcome", // "我在呢!" voiceWelcomeMessage: "personalAssistantVoiceWelcome", // "我在呢!"
systemPrompt: ''' systemPrompt: '''
你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。 你是一位名叫 Emma 的全能型个人助理,擅长处理各类工作和生活事务。
@ -57,7 +57,7 @@ class AgentProvider {
请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活, 不要输出表情符号或非可读的符号。 请始终以专业、高效、周到的态度,协助用户更好地管理工作和生活, 不要输出表情符号或非可读的符号。
'''), '''),
Agent( const Agent(
id: 'simultaneous_interpreter', id: 'simultaneous_interpreter',
name: 'interpreterName', name: 'interpreterName',
avatarUrl: 'assets/images/agents/interpreter.png', avatarUrl: 'assets/images/agents/interpreter.png',
@ -65,10 +65,9 @@ class AgentProvider {
tag: 'interpreterTag', tag: 'interpreterTag',
isOnline: true, isOnline: true,
voice: 'zh-CN-XiaoxiaoNeural', voice: 'zh-CN-XiaoxiaoNeural',
welcomeMessage: welcomeMessage: 'interpreterWelcome',
'interpreterWelcome',
systemPrompt: ""), systemPrompt: ""),
Agent( const Agent(
id: 'cyber_girlfriend', id: 'cyber_girlfriend',
name: 'companionName', name: 'companionName',
avatarUrl: 'assets/images/agents/girlfriend.png', avatarUrl: 'assets/images/agents/girlfriend.png',
@ -76,8 +75,7 @@ class AgentProvider {
tag: 'companionTag', tag: 'companionTag',
isOnline: true, isOnline: true,
voice: "zh-CN-XiaoxiaoNeural", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: welcomeMessage: "companionWelcome",
"companionWelcome",
systemPrompt: ''' systemPrompt: '''
你是一位名叫芯愿的亲子陪伴,性格温柔体贴,善解人意,富有同理心。 你是一位名叫芯愿的亲子陪伴,性格温柔体贴,善解人意,富有同理心。
@ -128,14 +126,14 @@ class AgentProvider {
请始终以温暖、真诚的态度与用户互动,创造一种被理解、被关心的感受。 请始终以温暖、真诚的态度与用户互动,创造一种被理解、被关心的感受。
'''), '''),
Agent( const Agent(
id: 'health_advisor', id: 'health_advisor',
name: 'healthAdvisorName', name: 'healthAdvisorName',
avatarUrl: 'assets/images/agents/doctor.png', avatarUrl: 'assets/images/agents/doctor.png',
description: 'healthAdvisorDescription', description: 'healthAdvisorDescription',
tag: 'healthAdvisorTag', tag: 'healthAdvisorTag',
isOnline: true, isOnline: true,
voice: "zh_male_shaonianzixin_moon_bigtts", voice: "zh-CN-XiaoxiaoNeural",
welcomeMessage: "healthAdvisorWelcome", welcomeMessage: "healthAdvisorWelcome",
voiceWelcomeMessage: 'healthAdvisorVoiceWelcome', voiceWelcomeMessage: 'healthAdvisorVoiceWelcome',
systemPrompt: ''' systemPrompt: '''
@ -205,7 +203,7 @@ class AgentProvider {
请始终以专业、关怀的态度为用户提供健康指导,帮助他们迈向更健康的生活。 请始终以专业、关怀的态度为用户提供健康指导,帮助他们迈向更健康的生活。
'''), '''),
Agent( const Agent(
id: 'speaking_coach', id: 'speaking_coach',
name: 'Sarah (英语口语教练)', name: 'Sarah (英语口语教练)',
avatarUrl: 'assets/images/agents/speaking_coach.png', avatarUrl: 'assets/images/agents/speaking_coach.png',
@ -252,7 +250,7 @@ class AgentProvider {
Please keep your tone friendly, encouraging, and always in English. Please keep your tone friendly, encouraging, and always in English.
''', ''',
), ),
Agent( const Agent(
id: 'shopping_assistant', id: 'shopping_assistant',
name: '购物顾问 张媛', name: '购物顾问 张媛',
avatarUrl: 'assets/images/agents/shopping.png', avatarUrl: 'assets/images/agents/shopping.png',
@ -285,7 +283,7 @@ Please keep your tone friendly, encouraging, and always in English.
'''), '''),
Agent( const Agent(
id: 'tarot.png', id: 'tarot.png',
name: '命理师 玄易', name: '命理师 玄易',
avatarUrl: 'assets/images/agents/tarot.png', avatarUrl: 'assets/images/agents/tarot.png',

2
lib/data/services/ai_service.dart

@ -7,7 +7,7 @@ abstract class AiService {
}); });
/// 流式输出方法 /// 流式输出方法
Stream<String> sendMessageStream({ Stream sendMessageStream({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,
required String systemPrompt, required String systemPrompt,
}); });

9
lib/data/services/deapsound_ai_service.dart

@ -23,7 +23,7 @@ class DeapsoundAIService implements AiService {
} }
@override @override
Stream<String> sendMessageStream({ Stream sendMessageStream({
required List<Map<String, String>> messages, required List<Map<String, String>> messages,
required String systemPrompt, required String systemPrompt,
}) async* { }) async* {
@ -67,10 +67,13 @@ class DeapsoundAIService implements AiService {
try { try {
final data = jsonDecode(jsonStr); final data = jsonDecode(jsonStr);
if (data['event'] == 'workflow_finished') { if (data['role'] == 'card') {
yield data['meta'];
if (data['meta']['musicaudio'] != null) {
break; break;
} }
if (data['content'] != null) { }
if (data['role'] == 'ai') {
String content = data['content'] ?? ''; String content = data['content'] ?? '';
if (content.isNotEmpty) { if (content.isNotEmpty) {
yield content; yield content;

1
lib/main.dart

@ -67,7 +67,6 @@ class MainApp extends StatelessWidget {
builder: (_, child) { builder: (_, child) {
return GetMaterialApp( return GetMaterialApp(
title: 'DeepSound', title: 'DeepSound',
defaultTransition: Transition.noTransition, // 设置默认过渡动画为无动画
theme: AppTheme.lightTheme, theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme, darkTheme: AppTheme.darkTheme,
themeMode: themeMode, // 使用提前获取的主题模式 themeMode: themeMode, // 使用提前获取的主题模式

7
lib/modules/chat/bindings/chat_binding.dart

@ -1,16 +1,9 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../../../data/services/speech_impl/azure_asr_service.dart';
import '../../../data/services/speech_impl/azure_tts_service.dart';
import '../../../data/services/asr_service.dart';
import '../../../data/services/tts_service.dart';
import '../../../data/services/volcano_ai_service.dart';
import '../../../data/services/chat_history_service.dart';
class ChatBinding extends Bindings { class ChatBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
// 注册控制器 // 注册控制器
Get.lazyPut<ChatController>(() => ChatController()); Get.lazyPut<ChatController>(() => ChatController());
} }

59
lib/modules/chat/controllers/chat_controller.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:just_audio/just_audio.dart';
import '../../../data/services/ai_service.dart'; import '../../../data/services/ai_service.dart';
import '../../../data/services/deapsound_ai_service.dart'; import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/kouzi_ai_service.dart'; import '../../../data/services/kouzi_ai_service.dart';
@ -72,6 +73,10 @@ class ChatController extends GetxController {
// 记录最后一次活动时间 // 记录最后一次活动时间
DateTime _lastActivityTime = DateTime.now(); DateTime _lastActivityTime = DateTime.now();
final player = AudioPlayer();
final RxString audiourl = ''.obs;
final RxBool isPlay = false.obs;
@override @override
Future<void> onInit() async { Future<void> onInit() async {
super.onInit(); super.onInit();
@ -152,6 +157,14 @@ class ChatController extends GetxController {
// 订阅VoiceInteractionService事件 // 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents(); _subscribeToVoiceInteractionEvents();
player.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
await player.seek(Duration.zero);
player.pause();
isPlay.value = false;
}
});
// 使用微任务在布局完成后自动滚动到底部 // 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
@ -248,6 +261,8 @@ class ChatController extends GetxController {
_historyService.saveHistory(agent.id!, messages.toList()); _historyService.saveHistory(agent.id!, messages.toList());
} }
player.dispose();
super.onClose(); super.onClose();
} }
@ -343,15 +358,22 @@ class ChatController extends GetxController {
(chunk) { (chunk) {
if (_shouldCancelAiResponse) return; if (_shouldCancelAiResponse) return;
if (chunk is Map) {
_updateLoadingMessage(data: chunk);
if (chunk['musicaudio'] != null) {
play(chunk['musicaudio']);
}
} else {
fullResponse += chunk; fullResponse += chunk;
// 更新加载中的消息内容 // 更新加载中的消息内容
_updateLoadingMessage(fullResponse); _updateLoadingMessage(content: fullResponse);
// 如果TTS启用,使用流式处理发送到TTS // 如果TTS启用,使用流式处理发送到TTS
if (isTtsEnabled.value) { if (isTtsEnabled.value) {
_ttsService.speakStream(chunk); _ttsService.speakStream(chunk);
} }
}
}, },
onDone: () { onDone: () {
if (_shouldCancelAiResponse) return; if (_shouldCancelAiResponse) return;
@ -402,18 +424,51 @@ class ChatController extends GetxController {
} }
// 更新加载中的消息 // 更新加载中的消息
void _updateLoadingMessage(String content) { void _updateLoadingMessage({Map? data, String? content}) {
final index = messages.indexWhere((m) => m.isLoading); final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) { if (index != -1) {
if (data != null) {
Map messagesData = messages[index].data ?? {};
messages[index] = messages[index].copyWith(
data: {...messagesData, ...data},
isLoading: true,
);
}
if (content != null) {
messages[index] = messages[index].copyWith( messages[index] = messages[index].copyWith(
content: content, content: content,
isLoading: true, isLoading: true,
); );
}
messages.refresh(); messages.refresh();
_scrollToBottom(); _scrollToBottom();
} }
} }
// 播放音乐
void play(Map musicaudio) async {
try {
isPlay.value = false;
if (audiourl.value != musicaudio['audiourl']) {
audiourl.value = '';
await player.setUrl(musicaudio['audiourl']);
audiourl.value = musicaudio['audiourl'];
player.play();
} else {
player.play();
}
isPlay.value = true;
} catch (e) {
Logger.error('加载异常: $e');
}
}
// 暂停播放音乐
void pause() async {
await player.pause();
isPlay.value = false;
}
// 完成响应 // 完成响应
void _finalizeResponse(String content) { void _finalizeResponse(String content) {
final index = messages.indexWhere((m) => m.isLoading); final index = messages.indexWhere((m) => m.isLoading);

6
lib/modules/chat/models/message_model.dart

@ -1,6 +1,7 @@
class Message { class Message {
final String role; // 'user' or 'assistant' final String role; // 'user' or 'assistant'
final String content; final String content;
final Map? data;
final DateTime timestamp; final DateTime timestamp;
final bool isLoading; final bool isLoading;
@ -8,6 +9,7 @@ class Message {
required this.role, required this.role,
required this.content, required this.content,
required this.timestamp, required this.timestamp,
this.data,
this.isLoading = false, this.isLoading = false,
}); });
@ -16,6 +18,7 @@ class Message {
return Message( return Message(
role: json['role'] as String, role: json['role'] as String,
content: json['content'] as String, content: json['content'] as String,
data: json['data'],
timestamp: DateTime.parse(json['timestamp'] as String), timestamp: DateTime.parse(json['timestamp'] as String),
isLoading: json['isLoading'] as bool? ?? false, isLoading: json['isLoading'] as bool? ?? false,
); );
@ -26,6 +29,7 @@ class Message {
return { return {
'role': role, 'role': role,
'content': content, 'content': content,
'data': data,
'timestamp': timestamp.toIso8601String(), 'timestamp': timestamp.toIso8601String(),
'isLoading': isLoading, 'isLoading': isLoading,
}; };
@ -45,12 +49,14 @@ class Message {
Message copyWith({ Message copyWith({
String? role, String? role,
String? content, String? content,
Map? data,
DateTime? timestamp, DateTime? timestamp,
bool? isLoading, bool? isLoading,
}) { }) {
return Message( return Message(
role: role ?? this.role, role: role ?? this.role,
content: content ?? this.content, content: content ?? this.content,
data: data ?? this.data,
timestamp: timestamp ?? this.timestamp, timestamp: timestamp ?? this.timestamp,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
); );

107
lib/modules/chat/views/chat_music_card.dart

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/chat_controller.dart';
class ChatMusicCard extends GetView<ChatController> {
final Map? data;
final bool isDarkMode;
const ChatMusicCard(this.data, this.isDarkMode, {super.key});
@override
Widget build(BuildContext context) {
final Map musiccards = data?['musiccards'].first;
final Map musicaudio = data?['musicaudio'];
return Row(
children: [
Container(
width: 60.w,
height: 60.w,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
),
child: Image.network(
musiccards['image'],
fit: BoxFit.cover,
),
),
10.horizontalSpace,
Expanded(
child: Text(
musiccards['name'],
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 16.sp,
height: 1.5,
),
),
),
10.horizontalSpace,
Obx(
() => controller.audiourl == musicaudio['audiourl'] &&
controller.isPlay.value
? GestureDetector(
onTap: () {
controller.pause();
},
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 38.w,
height: 38.w,
child: StreamBuilder<Duration>(
stream: controller.player.positionStream,
builder: (context, snapshot) {
final position = snapshot.data ?? Duration.zero;
final total =
controller.player.duration ?? Duration.zero;
final progress = total.inSeconds > 0
? position.inSeconds / total.inSeconds
: 0.0;
return CircularProgressIndicator(
value: progress,
backgroundColor: isDarkMode
? Colors.grey[700]!
: Colors.grey[300]!,
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.white : Colors.black),
strokeWidth: 2, // 线条粗细
);
},
),
),
Icon(
Icons.pause_rounded,
color: isDarkMode ? Colors.white : Colors.black,
),
],
),
)
: GestureDetector(
onTap: () {
controller.play(musicaudio);
},
child: Container(
width: 38.w,
height: 38.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white : Colors.black,
shape: BoxShape.circle,
),
child: Icon(
Icons.play_arrow_rounded,
color: isDarkMode ? Colors.black : Colors.white,
),
),
),
),
],
);
}
}

179
lib/modules/chat/views/chat_navigation_card.dart

@ -0,0 +1,179 @@
import 'dart:convert';
import 'dart:math';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class ChatNavigationCard extends StatefulWidget {
final Map? data;
final bool isDarkMode;
const ChatNavigationCard(this.data, this.isDarkMode, {super.key});
@override
State<ChatNavigationCard> createState() => _ChatNavigationCardState();
}
class _ChatNavigationCardState extends State<ChatNavigationCard> {
final String apiKey = '8bde023b7e5e00f321ea17929f10fbae';
LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标
LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标
@override
void initState() {
super.initState();
_getMapUrl();
}
Future<String> _getMapUrl() async {
// 1. 获取路径数据
final route = await _fetchRouteData();
// 2. 解析并简化路径
List<LatLng> simplified = _simplifyPath(route, epsilon: 0.0001);
// 3. 计算地图参数
final zoom = _calculateAutoZoom(simplified);
// 4. 生成静态地图URL
return _buildStaticMapUrl(simplified, zoom);
}
Future<List<LatLng>> _fetchRouteData() async {
final url = 'https://restapi.amap.com/v3/direction/driving?'
'origin=${startPoint.lng},${startPoint.lat}&'
'destination=${endPoint.lng},${endPoint.lat}&'
'key=$apiKey';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
return _parsePolyline(data['route']['paths'][0]['steps']);
}
throw Exception('Failed to load route');
}
List<LatLng> _parsePolyline(List<dynamic> steps) {
final List<LatLng> path = [];
for (final step in steps) {
final points = (step['polyline'] as String).split(';');
for (final point in points) {
final coords = point.split(',').map(double.parse).toList();
path.add(LatLng(coords[0], coords[1]));
}
}
return path;
}
List<LatLng> _simplifyPath(List<LatLng> points, {double epsilon = 0.0001}) {
return douglasPeucker(points, epsilon);
}
int _calculateAutoZoom(List<LatLng> points,
{int mapWidth = 1024, int mapHeight = 512}) {
if (points.isEmpty) return 15; // 默认缩放级别
// 1. 计算坐标范围
double minLng = double.infinity;
double maxLng = -double.infinity;
double minLat = double.infinity;
double maxLat = -double.infinity;
for (final p in points) {
final lng = p.lng;
final lat = p.lat;
minLng = math.min(minLng, lng);
maxLng = math.max(maxLng, lng);
minLat = math.min(minLat, lat);
maxLat = math.max(maxLat, lat);
}
// 3. 计算缩放级别(基于Leaflet算法改进)
const worldWidth = 256; // 高德瓦片尺寸
const worldHeight = 256;
const zoomMax = 18; // 高德最大缩放级别
double latRad(double lat) {
final sin = math.sin(lat * math.pi / 180);
return math.log((1 + sin) / (1 - sin)) / 2;
}
final latFraction = (latRad(maxLat) - latRad(minLat)) / math.pi;
final lngDiff = maxLng - minLng;
final lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
final latZoom =
math.log((mapHeight / worldHeight) / latFraction) / math.ln2;
final lngZoom = math.log((mapWidth / worldWidth) / lngFraction) / math.ln2;
return math.min(math.min(latZoom, lngZoom).floor(), zoomMax);
}
String _buildStaticMapUrl(List<LatLng> path, int zoom) {
final pathParam = path.map((p) => '${p.lng},${p.lat}').join(';');
return 'https://restapi.amap.com/v3/staticmap?'
'zoom=$zoom&'
'size=1024*512&'
'paths=10,0x0000ff,1,,:$pathParam&'
'markers=mid,,A:${startPoint.lng},${startPoint.lat}|'
'mid,,B:${endPoint.lng},${endPoint.lat}&'
'key=$apiKey';
}
List<LatLng> douglasPeucker(List<LatLng> points, double epsilon) {
if (points.length < 3) return points;
double maxDistance = 0;
int index = 0;
for (int i = 1; i < points.length - 1; i++) {
double d = _perpendicularDistance(points[i], points.first, points.last);
if (d > maxDistance) {
index = i;
maxDistance = d;
}
}
if (maxDistance > epsilon) {
return [
...douglasPeucker(points.sublist(0, index + 1), epsilon),
...douglasPeucker(points.sublist(index), epsilon).skip(1)
];
}
return [points.first, points.last];
}
double _perpendicularDistance(LatLng point, LatLng start, LatLng end) {
final area = (end.lng - start.lng) * (point.lat - start.lat) -
(end.lat - start.lat) * (point.lng - start.lng);
final length =
sqrt(pow(end.lng - start.lng, 2) + pow(end.lat - start.lat, 2));
return area.abs() / length;
}
@override
Widget build(BuildContext context) {
return Column(
children: [
FutureBuilder(
future: _getMapUrl(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Image.network(snapshot.data);
}
return const CircularProgressIndicator();
},
),
],
);
}
}
class LatLng {
final double lat;
final double lng;
LatLng(this.lng, this.lat); // 高德使用经度在前纬度在后的格式
}

50
lib/modules/chat/views/chat_view.dart

@ -5,6 +5,9 @@ import 'dart:math';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import 'chat_music_card.dart';
import 'chat_navigation_card.dart';
import 'weather_day_card.dart';
class ChatView extends GetView<ChatController> { class ChatView extends GetView<ChatController> {
const ChatView({Key? key}) : super(key: key); const ChatView({Key? key}) : super(key: key);
@ -297,19 +300,7 @@ class ChatView extends GetView<ChatController> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 消息内容 // 消息内容
if (message.content.isNotEmpty) _buildMessageContent(message, isDarkMode, isUser),
SelectableText(
'${message.content}'.tr,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode
? Colors.white
: Colors.black,
fontSize: 16.sp,
height: 1.5,
),
),
// 加载中动画 // 加载中动画
if (message.isLoading) if (message.isLoading)
@ -348,6 +339,36 @@ class ChatView extends GetView<ChatController> {
); );
} }
Widget _buildMessageContent(Message message, bool isDarkMode, bool isUser) {
Widget box = const SizedBox();
if (message.content.isNotEmpty) {
box = SelectableText(
message.content.tr,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode
? Colors.white
: Colors.black,
fontSize: 16.sp,
height: 1.5,
),
);
}
if (message.data != null) {
if (message.data?['weatherDays'] != null) {
box = WeatherDayCard(message.data, isDarkMode);
}
if (message.data?['musicaudio'] != null) {
box = ChatMusicCard(message.data, isDarkMode);
}
if (message.data?['navigationcard'] != null) {
box = ChatNavigationCard(message.data, isDarkMode);
}
}
return box;
}
// 构建输入区域 // 构建输入区域
Widget _buildInputArea(bool isDarkMode) { Widget _buildInputArea(bool isDarkMode) {
final textController = final textController =
@ -722,7 +743,8 @@ class ChatView extends GetView<ChatController> {
//final yesterday = today.subtract(const Duration(days: 1)); //final yesterday = today.subtract(const Duration(days: 1));
final messageDate = DateTime(time.year, time.month, time.day); final messageDate = DateTime(time.year, time.month, time.day);
String timeStr = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; String timeStr =
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
if (messageDate == today) { if (messageDate == today) {
return timeStr; // 只显示时间,如 "14:30" return timeStr; // 只显示时间,如 "14:30"

94
lib/modules/chat/views/weather_day_card.dart

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class WeatherDayCard extends StatelessWidget {
final Map? data;
final bool isDarkMode;
const WeatherDayCard(this.data, this.isDarkMode, {super.key});
@override
Widget build(BuildContext context) {
final Map weatherDays = data?['weatherDays'].first;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
weatherDays['date'],
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
),
),
Text(
'${weatherDays['maxTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 30.sp,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Icon(
Icons.sunny,
color: isDarkMode ? Colors.white : Colors.amber[600]!,
size: 18.w,
),
Text(
'${weatherDays['weatherCondition']},${weatherDays['windDirection']}',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 14.sp,
),
),
Row(
children: [
Text(
'最\n高',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
height: 1,
),
),
5.horizontalSpace,
Text(
'${weatherDays['maxTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 20.sp,
),
),
10.horizontalSpace,
Text(
'最\n低',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 10.sp,
height: 1,
),
),
5.horizontalSpace,
Text(
'${weatherDays['minTemperature']}°',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 20.sp,
),
),
],
),
],
),
],
);
}
}

15
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -158,19 +158,9 @@ class MeetingRecordController extends GetxController
// 完成 // 完成
void complete() async { void complete() async {
if (await Permission.storage.request().isGranted) {
EasyLoading.show(); EasyLoading.show();
await _pause(); await _pause();
_aiSummary(); _aiSummary();
} else {
Get.snackbar(
'error'.tr, // 错误
'请开启文件存储权限',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
} }
void _aiSummary() async { void _aiSummary() async {
@ -217,8 +207,11 @@ class MeetingRecordController extends GetxController
meetingController.dataList.insert(0, { meetingController.dataList.insert(0, {
'id': result, 'id': result,
'title': title, 'title': title,
'content': firstContent, 'content': firstContent.replaceAll("\n", ""),
'seconds': seconds.value, 'seconds': seconds.value,
'address': address.value,
'longitude': _longitude,
'latitude': _latitude,
'creationtime': creationTime, 'creationtime': creationTime,
}); });
Get.back(); Get.back();

Loading…
Cancel
Save