image

什么是maui

.NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动(ios,andriod)和桌面(windows,mac)应用。

image

chagpt

最近这玩意很火,由于网页版本限制了ip,还得必须开代理, 用起来比较麻烦,所以我尝试用maui开发一个聊天小应用 结合 chatgpt的开放api来实现(很多客户端使用网页版本接口用cookie的方式,有很多限制(如下图)总归不是很正规)

image

效果如下

image

mac端由于需要升级macos13才能开发调试,这部分我还没有完成,不过maui的控件是跨平台的,放在后续我升级系统再说

本项目开源

https://github.com/yuzd/maui_chatgpt

学习maui的老铁支持给个star

开发实战

我是设想开发一个类似jetbrains的ToolBox应用一样,启动程序在桌面右下角出现托盘图标,点击图标弹出应用(风格在windows mac平台保持一致)

需要实现的功能一览

  • 托盘图标(右键点击有menu)
  • webview(js和csharp互相调用)
  • 聊天SPA页面(react开发,build后让webview展示)

新建一个maui工程(vs2022)

image

坑一: 默认编译出来的exe是直接双击打不开的

image

工程文件加上这个配置

<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained Condition="'$(IsUnpackaged)' == 'true'">true</WindowsAppSDKSelfContained>
<SelfContained Condition="'$(IsUnpackaged)' == 'true'">true</SelfContained>

以上修改后,编译出来的exe双击就可以打开了

托盘图标(右键点击有menu)

启动时设置窗口不能改变大小,隐藏titlebar, 让Webview控件占满整个窗口

image

这里要根据平台不同实现不同了,windows平台采用winAPI调用,具体看工程代码吧

WebView

在MainPage.xaml 添加控件

image

对应的静态html等文件放在工程的 Resource\Raw文件夹下 (整个文件夹里面默认是作为内嵌资源打包的,工程文件里面的如下配置起的作用)

<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />

image

【重点】js和csharp互相调用

这部分我找了很多资料,最终参考了这个demo,然后改进了下

https://github.com/mahop-net/Maui.HybridWebView

主要原理是:

  • js调用csharp方法前先把数据存储在localstorage里
  • 然后windows.location切换特定的url发起调用,返回一个promise,等待csharp的事件
  • csharp端监听webview的Navigating事件,异步进行下面处理
  • 根据url解析出来localstorage的key
  • 然后csharp端调用excutescript根据key拿到localstorage的value
  • 进行逻辑处理后返回通过事件分发到js端

js的调用封装如下:


// 调用csharp的方法封装
export default class CsharpMethod {
  constructor(command, data) {
    this.RequestPrefix = "request_csharp_";
    this.ResponsePrefix = "response_csharp_";
    // 唯一
    this.dataId = this.RequestPrefix + new Date().getTime();
    // 调用csharp的命令
    this.command = command;
    // 参数
    this.data = { command: command, data: !data ? '' : JSON.stringify(data), key: this.dataId }
  }   // 调用csharp 返回promise
  call() {
    // 把data存储到localstorage中 目的是让csharp端获取参数
    localStorage.setItem(this.dataId, this.utf8_to_b64(JSON.stringify(this.data)));
    let eventKey = this.dataId.replace(this.RequestPrefix, this.ResponsePrefix);
    let that = this;
    const promise = new Promise(function (resolve, reject) {
      const eventHandler = function (e) {
        window.removeEventListener(eventKey, eventHandler);
        let resp = e.newValue;
        if (resp) {
          // 从base64转换
          let realData = that.b64_to_utf8(resp);
          if (realData.startsWith('err:')) {
            reject(realData.substr(4));
          } else {
            resolve(realData);
          }
        } else {
          reject("unknown error : " + eventKey);
        }
      };
      // 注册监听回调(csharp端处理完发起的)
      window.addEventListener(eventKey, eventHandler);
    });
    // 改变location 发送给csharp端
    window.location = "/api/" + this.dataId;
    return promise;
  }   // 转成base64 解决中文乱码
  utf8_to_b64(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
  }
  // 从base64转过来 解决中文乱码
  b64_to_utf8(str) {
    return decodeURIComponent(escape(window.atob(str)));
  } }

前端的使用方式

import CsharpMethod from '../../services/api'

// 发起调用csharp的chat事件函数
const method = new CsharpMethod("chat", {msg: message});
method.call() // call返回promise
.then(data =>{
  // 拿到csharp端的返回后展示
  onMessageHandler({
    message: data,
    username: 'Robot',
    type: 'chat_message'
  });
}).catch(err =>  {
    alert(err);
});

csharp端的处理:

image

这么封装后,js和csharp的互相调用就很方便了

chatgpt的开放api调用

注册号chatgpt后可以申请一个APIKEY

image

API封装:

  public static async Task<CompletionsResponse> GetResponseDataAsync(string prompt)
        {
            // Set up the API URL and API key
            string apiUrl = "https://api.openai.com/v1/completions";             // Get the request body JSON
            decimal temperature = decimal.Parse(Setting.Temperature, CultureInfo.InvariantCulture);
            int maxTokens = int.Parse(Setting.MaxTokens, CultureInfo.InvariantCulture);
            string requestBodyJson = GetRequestBodyJson(prompt, temperature, maxTokens);             // Send the API request and get the response data
            return await SendApiRequestAsync(apiUrl, Setting.ApiKey, requestBodyJson);
        }         private static string GetRequestBodyJson(string prompt, decimal temperature, int maxTokens)
        {
            // Set up the request body
            var requestBody = new CompletionsRequestBody
            {
                Model = "text-davinci-003",
                Prompt = prompt,
                Temperature = temperature,
                MaxTokens = maxTokens,
                TopP = 1.0m,
                FrequencyPenalty = 0.0m,
                PresencePenalty = 0.0m,
                N = 1,
                Stop = "[END]",
            };             // Create a new JsonSerializerOptions object with the IgnoreNullValues and IgnoreReadOnlyProperties properties set to true
            var serializerOptions = new JsonSerializerOptions
            {
                IgnoreNullValues = true,
                IgnoreReadOnlyProperties = true,
            };             // Serialize the request body to JSON using the JsonSerializer.Serialize method overload that takes a JsonSerializerOptions parameter
            return JsonSerializer.Serialize(requestBody, serializerOptions);
        }         private static async Task<CompletionsResponse> SendApiRequestAsync(string apiUrl, string apiKey, string requestBodyJson)
        {
            // Create a new HttpClient for making the API request
            using HttpClient client = new HttpClient();             // Set the API key in the request headers
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + apiKey);             // Create a new StringContent object with the JSON payload and the correct content type
            StringContent content = new StringContent(requestBodyJson, Encoding.UTF8, "application/json");             // Send the API request and get the response
            HttpResponseMessage response = await client.PostAsync(apiUrl, content);             // Deserialize the response
            var responseBody = await response.Content.ReadAsStringAsync();             // Return the response data
            return JsonSerializer.Deserialize<CompletionsResponse>(responseBody);
        }

调用方式

  var reply = await ChatService.GetResponseDataAsync('xxxxxxxxxx');

完整代码参考 https://github.com/yuzd/maui_chatgpt

在学习maui的过程中,遇到问题我在microsoft learn提问,回答的效率很快,推荐大家试试看

image

关于我

image

微软最有价值专家是微软公司授予第三方技术专业人士的一个全球奖项。27年来,世界各地的技术社区领导者,因其在线上和线下的技术社区中分享专业知识和经验而获得此奖项。

MVP是经过严格挑选的专家团队,他们代表着技术最精湛且最具智慧的人,是对社区投入极大的热情并乐于助人的专家。MVP致力于通过演讲、论坛问答、创建网站、撰写博客、分享视频、开源项目、组织会议等方式来帮助他人,并最大程度地帮助微软技术社区用户使用Microsoft技术。

更多详情请登录官方网站https://mvp.microsoft.com/zh-cn

微软跨平台maui开发chatgpt客户端的更多相关文章

  1. 新成员!Visual Studio Code --跨平台的开发工具(支持OSX, Linux 和 Windows)

    原文出处:新成员!Visual Studio Code --跨平台的开发工具(支持OSX, Linux 和 Windows) 这是我的文章备份  http://www.dotblogs.com.tw/ ...

  2. 跨平台移动开发工具:PhoneGap与Titanium全方位比拼

    PhoneGap和Appcelerator Titanium,对于封装和配置移动应用程序而言,二者都是非常受欢迎的开源JavaScript框架.本文为Appcelerator开发者Kevin Whin ...

  3. 前端开发福音!阿里Weex跨平台移动开发工具开源-b

    阿里巴巴今天在Qcon大会上宣布跨平台移动开发工具Weex开放内测邀请.Weex能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持iOS.安卓.YunOS及 ...

  4. 微软宣布.NET开发环境将开源 支持Mac OS X和Linux

    微软宣布.NET开发环境将开源 支持Mac OS X和Linux 投递人 itwriter 发布于 2014-11-13 06:58 评论(55) 有4388人阅读  原文链接  [收藏]  « » ...

  5. 微软宣布.NET开发环境将开源 支持三大操作系统(windows,Mac OS X和Linux)(转)

    微软周三(11月12日)公布了.NET开发框架开源计划.公司拟将这长期以来只能运行于Windows系统下的开发环境,通过GitHub开源,以实现跨平台支持Mac OS X和Linux.根据微软公布的计 ...

  6. 带你从零学ReactNative开发跨平台App开发(六)

    ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...

  7. 微软发布了开发社区采用.NET Standard的最新信息

    最近,微软发布了开发社区当前采用.NET Standard的最新信息..NET Standard是API的正式规范,现有.NET实现在不同平台的是通用的(从而允许跨平台开发).当前规范(版本2.0)在 ...

  8. Flutter与Xamarin跨平台移动开发相比

    在过去十年中,移动行业经历了巨大的增长,特别是在应用程序开发方面.据Statista报告称,全球智能手机用户超过20亿,预计到2022年底这一数字将增加到50亿以上.在这些智能手机中,近100%在三个 ...

  9. [.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux)

    [.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux) 1.关于跨平台 上篇中介绍了MVC的发展历程,说到ASP.NET ...

  10. Jsoup开发网站客户端第二篇,图片轮播,ScrollView兼容ListView

    最近一段日子忙的焦头烂额,代码重构,新项目编码,导致jsoup开发网站客户端也没时间继续下去,只能利用晚上时间去研究了.今天实现美食网首页图片轮播效果,网站效果图跟Android客户端实现如图: 从浏 ...

随机推荐

  1. Git Review + Gerrit 安装及使用完成 Code-Review

    转载自:https://cloud.tencent.com/developer/article/1010615 1.Code Review 介绍 Code Review 代码评审是指在软件开发过程中, ...

  2. OpenglEs之三角形绘制

    在前面我们已经在NDK层搭建好了EGL环境,也介绍了一些着色器相关的理论知识,那么这次我们就使用已经搭配的EGL绘制一个三角形吧. 在Opengl ES的世界中,无论多复杂的形状都是由点.线或三角形组 ...

  3. PHP全栈开发(八):CSS Ⅰ 选择器

    直到目前为止,我们把从HTML中的数据是如何通过PHP到服务器端,然后又通过PHP到数据库,然后从数据库中出来,通过PHP到HTML的整个过程通过一个案例过了一遍. 可以说,这些才刚刚开始.下面我们开 ...

  4. Android 13 新特性及适配指南

    Android 13(API 33)于 2022年8月15日 正式发布(发布时间较往年早了一些),正式版Release源代码也于当日被推送到AOSP Android开源项目. 截止到笔者撰写这篇文章时 ...

  5. 一步一图带你深入理解 Linux 虚拟内存管理

    写在本文开始之前.... 从本文开始我们就正式开启了 Linux 内核内存管理子系统源码解析系列,笔者还是会秉承之前系列文章的风格,采用一步一图的方式先是详细介绍相关原理,在保证大家清晰理解原理的基础 ...

  6. C++ set集合容器用法解析

    1.简介 set是C++STL库中的一个容器,他十分的便利,所有的元素插入时都会被自动排序,并且容器内保证元素不重复,就想高一数学中讲的集合具有互异性一样,(好像set本来就叫集合容器 bushi)2 ...

  7. Charles基本功能

    Windows: 运行安装应用程序以在程序菜单中安装 Charles. Mac OS X: 通过双击解压缩下载文件,然后将 Charles 应用程序复制到 Applications 目录中. Linu ...

  8. 恭喜磊哥喜提n+1

    昨天下午两点多磊哥突然喊我下楼,第一反应是"这孙子,抽烟就直说,还说个事,你以外你是吉祥村大姐啊". 心里骂完以后我慢慢悠悠下楼了,见他在打电话我先默默点上一支,准备待他结束以后对 ...

  9. Intel GPU Gen 9 架构

    * 参考spec:the-compute-architecture-of-intel-processor-graphics-gen9-v1d0.pdf SOC 架构 Gen9 架构是早期用在igpu ...

  10. I-图的分割(二分+并查集)

    图的分割 题目大意: 给你n个点,m条边的图,没有重环和自环,所有的点都联通 可以通过删除几条边使得整个图变成两个联通子图 求删除的边中最大边权的最小值 解题思路: 看到"最大边权的最小值& ...