2025-10-11 18:25:59 +08:00

481 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using XiaoZhiSharp_MauiApp.Services;
using System.Collections.Specialized;
namespace XiaoZhiSharp_MauiApp
{
public partial class MainPage : ContentPage
{
private readonly XiaoZhi_AgentService _agentService;
private readonly ICameraService? _cameraService;
private System.Timers.Timer _updateTimer;
private bool _isUpdatingUI = false;
public MainPage(XiaoZhi_AgentService agentService, ICameraService? cameraService = null)
{
_cameraService = cameraService;
InitializeComponent();
_agentService = agentService;
BindingContext = _agentService;
// 监听聊天历史变化
_agentService.ChatHistory.CollectionChanged += OnChatHistoryChanged;
// 启动定时器更新UI主要用于更新音频强度等实时数据
_updateTimer = new System.Timers.Timer(100);
_updateTimer.Elapsed += OnTimerElapsed;
_updateTimer.Start();
}
private void OnChatHistoryChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MainThread.BeginInvokeOnMainThread(() =>
{
UpdateChatMessages();
});
}
private void UpdateChatMessages()
{
// 如果没有聊天记录,显示欢迎消息
WelcomeFrame.IsVisible = _agentService.ChatHistory.Count == 0;
// 清除现有的消息(除了欢迎消息)
var messagesToRemove = ChatMessagesLayout.Children
.Where(c => c != WelcomeFrame)
.ToList();
foreach (var message in messagesToRemove)
{
ChatMessagesLayout.Children.Remove(message);
}
// 添加所有聊天消息
foreach (var message in _agentService.ChatHistory)
{
AddMessageToUI(message);
}
// 滚动到底部
_ = ScrollToBottom();
}
private void AddMessageToUI(ChatMessage message)
{
// 时间标签
var timeLabel = new Label
{
Text = message.Timestamp.ToString("HH:mm"),
FontSize = 12,
TextColor = Colors.Gray,
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 5)
};
ChatMessagesLayout.Children.Add(timeLabel);
// 消息框架
var messageFrame = new Frame
{
Padding = new Thickness(10, 8),
CornerRadius = 10,
HasShadow = false,
Margin = new Thickness(0, 2)
};
// 创建消息内容容器
var messageContainer = new VerticalStackLayout { Spacing = 8 };
// 如果有图片,先添加图片
if (!string.IsNullOrEmpty(message.ImagePath) && File.Exists(message.ImagePath))
{
var imageView = new Image
{
Source = ImageSource.FromFile(message.ImagePath),
Aspect = Aspect.AspectFit,
MaximumHeightRequest = 200,
MaximumWidthRequest = 250,
BackgroundColor = Colors.LightGray
};
// 添加图片点击事件,可以查看大图
var tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += async (s, e) => await ShowImageFullscreen(message.ImagePath);
imageView.GestureRecognizers.Add(tapGesture);
messageContainer.Children.Add(imageView);
}
// 添加文本消息
var messageLabel = new Label
{
Text = message.Content,
FontSize = 14,
LineBreakMode = LineBreakMode.WordWrap
};
messageContainer.Children.Add(messageLabel);
if (message.IsUser)
{
messageFrame.BackgroundColor = Color.FromHex("#dcf8c6");
messageFrame.HorizontalOptions = LayoutOptions.End;
messageFrame.Margin = new Thickness(50, 2, 0, 2);
messageLabel.TextColor = Colors.Black;
}
else
{
messageFrame.BackgroundColor = Colors.White;
messageFrame.HorizontalOptions = LayoutOptions.Start;
messageFrame.Margin = new Thickness(0, 2, 50, 2);
messageLabel.TextColor = Colors.Black;
}
messageFrame.Content = messageContainer;
ChatMessagesLayout.Children.Add(messageFrame);
}
private async Task ScrollToBottom()
{
await Task.Delay(100); // 等待UI更新
await ChatScrollView.ScrollToAsync(0, ChatMessagesLayout.Height, false);
}
private async void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_isUpdatingUI) return;
_isUpdatingUI = true;
await MainThread.InvokeOnMainThreadAsync(() =>
{
// 由于XiaoZhi_AgentService已经实现了INotifyPropertyChanged
// 它的属性变化会自动通知UI更新所以这里什么都不需要做
// 定时器主要是为了确保UI能及时响应
});
_isUpdatingUI = false;
}
private async void OnRecordingButtonClicked(object sender, EventArgs e)
{
try
{
if (_agentService.IsRecording)
{
await _agentService.Agent.StopRecording();
}
else
{
await _agentService.Agent.StartRecording("auto");
}
}
catch (Exception ex)
{
await DisplayAlert("错误", $"录音操作失败: {ex.Message}", "确定");
}
}
private async void OnStopChatClicked(object sender, EventArgs e)
{
try
{
await _agentService.Agent.ChatAbort();
}
catch (Exception ex)
{
await DisplayAlert("错误", $"停止对话失败: {ex.Message}", "确定");
}
}
private async void OnCameraButtonClicked(object sender, EventArgs e)
{
try
{
// 先检查是否有摄像头服务
if (_cameraService == null)
{
await DisplayAlert("提示", "摄像头功能不可用", "确定");
return;
}
// 检查设备支持
if (!_cameraService.IsSupported)
{
await DisplayAlert("提示", "设备不支持摄像头功能", "确定");
return;
}
// 检查权限
if (!_cameraService.HasPermission)
{
var granted = await _cameraService.RequestPermissionAsync();
if (!granted)
{
await DisplayAlert("提示", "需要摄像头权限才能使用此功能", "确定");
return;
}
}
try
{
// 第一步:直接打开相机拍照
await DisplayAlert("拍照提示", "即将打开相机,请拍摄您要识别的内容", "确定");
var imageData = await _cameraService.CapturePhotoAsync();
if (imageData == null || imageData.Length == 0)
{
await DisplayAlert("提示", "拍照失败或已取消", "确定");
return;
}
//XiaoZhiSharp.Services.ImageStorageService imageStorageService = new XiaoZhiSharp.Services.ImageStorageService();
//imageStorageService.PostImage("https://coze.nbee.net/image/v1/stream/1", "", "deviceId", "clientId", imageData);
// 第二步:保存照片并在聊天记录中显示
string imagePath = await SaveImageToLocal(imageData);
// 第三步:拍照成功后询问问题(可选)
var question = await DisplayPromptAsync("拍照成功",
"拍照成功!请输入您要询问关于这张图片的问题:\n(直接点击确定将使用默认问题)",
"确定", "取消",
"请描述这张图片的内容",
maxLength: 200);
if (question == null) // 用户点击取消
{
return;
}
if (string.IsNullOrWhiteSpace(question))
{
question = "请描述这张图片的内容"; // 默认问题
}
// 第四步:在聊天记录中显示用户的问题和图片
var userMessage = $"📷 {question}";
var imageMessage = new ChatMessage(userMessage, true) { ImagePath = imagePath };
_agentService.ChatHistory.Add(imageMessage);
await ScrollToBottom();
// 第五步显示AI识别进度
var progressMessage = $"正在进行AI识别...\n问题: {question}";
var loadingTask = DisplayAlert("AI识别中", progressMessage, "请稍候");
try
{
// 第六步调用AI识别服务直接对已拍摄的图片进行识别
var result = await _cameraService.ExplainImageAsync(imageData, question);
// 第七步:解析并显示结果
if (!string.IsNullOrEmpty(result))
{
// AI的回答
var aiResponse = $"根据您拍摄的图片,{ExtractAIResponse(result)}";
// 添加到聊天记录
await Task.Delay(300);
_agentService.ChatHistory.Add(new ChatMessage(aiResponse, false));
await ScrollToBottom();
}
else
{
await DisplayAlert("识别失败", "AI识别失败请重试", "确定");
}
}
catch (Exception ex)
{
await DisplayAlert("错误", $"AI识别失败: {ex.Message}", "确定");
}
}
catch (Exception ex)
{
await DisplayAlert("错误", $"拍照过程出错: {ex.Message}", "确定");
}
}
catch (Exception ex)
{
await DisplayAlert("错误", $"摄像头功能出错: {ex.Message}", "确定");
}
}
private async Task<string> SaveImageToLocal(byte[] imageData)
{
try
{
// 创建应用文件夹
var appDataPath = FileSystem.CacheDirectory;
var imagesPath = Path.Combine(appDataPath, "CapturedImages");
if (!Directory.Exists(imagesPath))
{
Directory.CreateDirectory(imagesPath);
}
// 生成文件名
var fileName = $"camera_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";
var filePath = Path.Combine(imagesPath, fileName);
// 保存文件
await File.WriteAllBytesAsync(filePath, imageData);
return filePath;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"保存图片失败: {ex.Message}");
return string.Empty;
}
}
private string ExtractAIResponse(string jsonResult)
{
try
{
// 简单的JSON解析提取AI回答
if (jsonResult.Contains("\"success\": true"))
{
// 如果有结果字段,提取结果
var start = jsonResult.IndexOf("\"result\":");
if (start >= 0)
{
start = jsonResult.IndexOf("\"", start + 9) + 1;
var end = jsonResult.IndexOf("\"", start);
if (end > start)
{
return jsonResult.Substring(start, end - start);
}
}
// 如果有message字段提取消息
start = jsonResult.IndexOf("\"message\":");
if (start >= 0)
{
start = jsonResult.IndexOf("\"", start + 10) + 1;
var end = jsonResult.IndexOf("\"", start);
if (end > start)
{
return jsonResult.Substring(start, end - start);
}
}
return "AI识别成功但未获取到具体描述。";
}
else
{
// 提取错误消息
var start = jsonResult.IndexOf("\"message\":");
if (start >= 0)
{
start = jsonResult.IndexOf("\"", start + 10) + 1;
var end = jsonResult.IndexOf("\"", start);
if (end > start)
{
return $"识别失败: {jsonResult.Substring(start, end - start)}";
}
}
return "识别失败,请重试。";
}
}
catch
{
return jsonResult; // 如果解析失败,返回原始结果
}
}
private async void OnMessageSendClicked(object sender, EventArgs e)
{
var message = MessageEntry.Text?.Trim();
if (!string.IsNullOrWhiteSpace(message))
{
MessageEntry.Text = string.Empty;
await _agentService.Agent.ChatMessage(message);
await ScrollToBottom();
}
}
private void OnClearChatClicked(object sender, EventArgs e)
{
_agentService.ClearChatHistory();
UpdateChatMessages();
}
private void OnChatPageClicked(object sender, TappedEventArgs e)
{
// 当前已在聊天页面
}
private async void OnSettingsPageClicked(object sender, TappedEventArgs e)
{
await Navigation.PushAsync(new SettingsPage(_agentService));
}
private async Task ShowImageFullscreen(string imagePath)
{
try
{
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
{
await DisplayAlert("提示", "图片文件不存在", "确定");
return;
}
// 创建全屏图片页面
var fullscreenPage = new ContentPage
{
Title = "查看图片",
BackgroundColor = Colors.Black
};
var imageView = new Image
{
Source = ImageSource.FromFile(imagePath),
Aspect = Aspect.AspectFit,
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand
};
// 添加点击关闭功能
var tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += async (s, e) => await Navigation.PopModalAsync();
imageView.GestureRecognizers.Add(tapGesture);
var closeButton = new Button
{
Text = "关闭",
BackgroundColor = Colors.Gray,
TextColor = Colors.White,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 0, 50)
};
closeButton.Clicked += async (s, e) => await Navigation.PopModalAsync();
var grid = new Grid
{
RowDefinitions = new RowDefinitionCollection
{
new RowDefinition(GridLength.Star),
new RowDefinition(GridLength.Auto)
}
};
grid.Children.Add(imageView);
Grid.SetRow(imageView, 0);
grid.Children.Add(closeButton);
Grid.SetRow(closeButton, 1);
fullscreenPage.Content = grid;
await Navigation.PushModalAsync(fullscreenPage);
}
catch (Exception ex)
{
await DisplayAlert("错误", $"显示图片失败: {ex.Message}", "确定");
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_updateTimer?.Stop();
_updateTimer?.Dispose();
}
}
}