微軟跨平臺maui開發chatgpt客戶端

89e85cef9592e48366e13e1e9f60740e.png

image

什么是maui

.NET 多平臺應用 UI (.NET MAUI) 是一個跨平臺框架,用于使用 C# 和 XAML 創建本機移動(ios,andriod)和桌面(windows,mac)應用。

f237fc7c179851dd45c34c32d5889583.png
image

chagpt

最近這玩意很火,由于網頁版本限制了ip,還得必須開代理, 用起來比較麻煩,所以我嘗試用maui開發一個聊天小應用 結合 chatgpt的開放api來實現(很多客戶端使用網頁版本接口用cookie的方式,有很多限制(如下圖)總歸不是很正規)

1e40f7182a038a2bd9792cf06f30c2a4.png
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)

074130d40dc684c6ffba06a54e355c0e.png
image

坑一:默認編譯出來的exe是直接雙擊打不開的

1a4f8f41edebead54f4cde6794b2b2d4.png
image

工程文件加上這個配置

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

以上修改后,編譯出來的exe雙擊就可以打開了

托盤圖標(右鍵點擊有menu)

啟動時設置窗口不能改變大小,隱藏titlebar, 讓Webview控件占滿整個窗口

a001e9262446610770db17045c93818d.png
image

這里要根據平臺不同實現不同了,windows平臺采用winAPI調用,具體看工程代碼吧

WebView

在MainPage.xaml 添加控件

026c2ebee8101f666b5e83b4b278285b.png
image

對應的靜態html等文件放在工程的 Resource\Raw文件夾下 (整個文件夾里面默認是作為內嵌資源打包的,工程文件里面的如下配置起的作用)

<!--?Raw?Assets?(also?remove?the?"Resources\Raw"?prefix)?-->
<MauiAsset?Include="Resources\Raw\**"?LogicalName="%(RecursiveDir)%(Filename)%(Extension)"?/>
7cd8c14ed449840bc3101f3084699d81.png
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?返回promisecall()?{//?把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端的處理:

d4954f57970b1949c40d59653783a729.png
image

這么封裝后,js和csharp的互相調用就很方便了

chatgpt的開放api調用

注冊號chatgpt后可以申請一個APIKEY

08037ac37c1c5eb819935e4d064172d4.png
image

API封裝:

public?static?async?Task<CompletionsResponse>?GetResponseDataAsync(string?prompt){//?Set?up?the?API?URL?and?API?keystring?apiUrl?=?"https://api.openai.com/v1/completions";//?Get?the?request?body?JSONdecimal?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?datareturn?await?SendApiRequestAsync(apiUrl,?Setting.ApiKey,?requestBodyJson);}private?static?string?GetRequestBodyJson(string?prompt,?decimal?temperature,?int?maxTokens){//?Set?up?the?request?bodyvar?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?truevar?serializerOptions?=?new?JsonSerializerOptions{IgnoreNullValues?=?true,IgnoreReadOnlyProperties?=?true,};//?Serialize?the?request?body?to?JSON?using?the?JsonSerializer.Serialize?method?overload?that?takes?a?JsonSerializerOptions?parameterreturn?JsonSerializer.Serialize(requestBody,?serializerOptions);}private?static?async?Task<CompletionsResponse>?SendApiRequestAsync(string?apiUrl,?string?apiKey,?string?requestBodyJson){//?Create?a?new?HttpClient?for?making?the?API?requestusing?HttpClient?client?=?new?HttpClient();//?Set?the?API?key?in?the?request?headersclient.DefaultRequestHeaders.Add("Authorization",?"Bearer?"?+?apiKey);//?Create?a?new?StringContent?object?with?the?JSON?payload?and?the?correct?content?typeStringContent?content?=?new?StringContent(requestBodyJson,?Encoding.UTF8,?"application/json");//?Send?the?API?request?and?get?the?responseHttpResponseMessage?response?=?await?client.PostAsync(apiUrl,?content);//?Deserialize?the?responsevar?responseBody?=?await?response.Content.ReadAsStringAsync();//?Return?the?response?datareturn?JsonSerializer.Deserialize<CompletionsResponse>(responseBody);}

調用方式

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

完整代碼參考 https://github.com/yuzd/maui_chatgpt

在學習maui的過程中,遇到問題我在microsoft learn提問,回答的效率很快,推薦大家試試看

975ccd35aacfae2a0eaae5090c371a41.png
image

關于我

942047c70d6d119ffe6383e313d923f5.png
image

微軟最有價值專家是微軟公司授予第三方技術專業人士的一個全球獎項。27年來,世界各地的技術社區領導者,因其在線上和線下的技術社區中分享專業知識和經驗而獲得此獎項。

MVP是經過嚴格挑選的專家團隊,他們代表著技術最精湛且最具智慧的人,是對社區投入極大的熱情并樂于助人的專家。MVP致力于通過演講、論壇問答、創建網站、撰寫博客、分享視頻、開源項目、組織會議等方式來幫助他人,并最大程度地幫助微軟技術社區用戶使用Microsoft技術。

更多詳情請登錄官方網站https://mvp.microsoft.com/zh-cn

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280115.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280115.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280115.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在Xshell 6開NumLock時按小鍵盤上的數字鍵并不能輸入數字

小鍵盤問題 在Xshell 6上用vi的時候&#xff0c;開NumLock時按小鍵盤上的數字鍵并不能輸入數字&#xff0c;而是出現一個字母然后換行&#xff08;實際上是命令模式上對應上下左右的鍵&#xff09;。解決方法 選項Terminal->Features里&#xff0c;找到Disable application …

WebP 在減少圖片體積和流量上的效果如何?—— WebP 技術實踐分享

作者 | Jackson編輯 | 尾尾 不論是 PC 還是移動端&#xff0c;圖片一直占據著頁面流量的大頭&#xff0c;在圖片的大小和質量之間如何權衡&#xff0c;成為了長期困擾開發者們的問題。而 WebP 技術的出現&#xff0c;為解決該問題提供了好的方案。本文將為大家詳細介紹 WebP 技…

chrome 固定縮放比例_您如何調整Google Chrome瀏覽器的用戶界面縮放比例?

chrome 固定縮放比例Everything can be going along nicely until a program gets a new update that suddenly turns everything into a visual mess, like scaling up the UI, for example. Is there a simple solution? Today’s SuperUser Q&A post has some helpful …

樹莓派 Raspberry Pi 更換國內源

http://www.shumeipaiba.com/wanpai/jiaocheng/16.html轉載于:https://www.cnblogs.com/Baronboy/p/9185849.html

優雅告別 2022 年,2023 年主題:敢想,就敢做!

自從工作之后&#xff0c;每年春節我都會花一天時間&#xff0c;一個人待在一個小房間&#xff0c;思考自己今年做了什么具備階段性成果的事情。然后&#xff0c;寫下明年需要執行的計劃。會寫在一個 XMind 文件里&#xff0c;記錄每一年將要執行的計劃&#xff0c;且未完成的計…

純js上傳文件 很好用

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>參數設置</title> <meta name"keywords&q…

買臺電腦,不行!去旅游一下,不行!論程序員怎么實現財務自由!

“最近讀了一本不是編程的程序員技能書《軟技能-代碼之外的生存指南》&#xff0c;全書分為 7 個篇章&#xff0c;分別是職業、自我營銷、學習、生產力、理財、健身和精神。在讀完職業、自我營銷和理財這三個篇章后&#xff0c;讓我感觸很深&#xff0c;也讓我很意外。本來以為…

java發送gmail_如何在Gmail中輕松通過電子郵件發送人群

java發送gmailMailing lists are an old tool in the email arsenal, but their implementation in Gmail isn’t immediately intuitive. Read on as we show you how to email groups using your Gmail account. 郵件列表是電子郵件庫中的一個舊工具&#xff0c;但是在Gmail中…

移動web開發相關筆記(三)

1.推薦以sublime插件的排名官網:https://packagecontrol.io/&#xff08;sublime插件官網&#xff09;2.時間算法【//總秒數var totalSecond 3671;//獲取里面的小時var hoursMath.floor(totalSecond/3600);//獲取剩下的分鐘var minuteMath.floor(totalSecond%3600/60);//獲取剩…

互聯網和IT行業越來越嚴峻,前景幾何?

楔子新冠疫情反反復復&#xff0c;互聯網和IT行業一路下滑。硅谷裁員高達10萬人。前景該何處何從呢&#xff1f;春江水暖豬先知IT行業如此的不景氣&#xff0c;自然是一些在風口上被吹起來的豬首先感受到了。他們進行的自救其實就一條:裁員&#xff0c;大量的裁員。裁員可以解決…

Asp.net MVC使用Model Binding解除Session, Cookie等依賴

上篇文章"Asp.net MVC使用Filter解除Session, Cookie等依賴"介紹了如何使用Filter來解除對于Session, Cookie的依賴。其實這個也可以通過Model Binding來達到同樣的效果。 什么是Model Binding? Model Binding的作用就是將Request請求中包含的散亂參數&#xff0c;根…

C++回聲服務器_4-UDP connect版本客戶端

針對UDP套接字調用connect函數不會與對方UDP套接字建立連接&#xff0c;只是向UDP套接字注冊目標IP和端口信息。 修改客戶端代碼 服務器代碼不需要修改&#xff0c;只需修改客戶端代碼。調用connect函數之后&#xff0c;可以調用write函數和read函數來發送、接收數據&#xff0…

如何在路由綁定中使用 IParsable

IParsable 是 .Net 7 中新增的接口&#xff0c;它可以將字符串轉換為對應的實體。在 Controller 的 Route 綁定中可以使用 IParsable 來綁定復雜的實體。實驗背景 假設有一個需要將 route "report/{month}-{day}" 綁定到 MyDate 對象上的場景。在 .Net 7 之前&#x…

火狐 新增標簽 一直加載_在Firefox的新標簽頁中加載最后標簽頁的URL

火狐 新增標簽 一直加載Yeah, you’re pretty sure that you’re the master of all things Firefox. I mean, why else would you be reading this article? So, we’ve got to ask, have you ever seen this one before? 是的&#xff0c;您很確定自己是Firefox的所有人。 …

ptyhon【遞歸練習】

轉載于:https://www.cnblogs.com/LTEF/p/9187287.html

Iterator 和 for...of 循環

本系列屬于阮一峰老師所著的ECMAScript 6 入門學習筆記 Iterator(遍歷器) JavaScript表示“集合”的數據結構&#xff0c;除了Array 、Object &#xff0c;ES6又新增了Map 和Set 。 遍歷器&#xff08;Iterator&#xff09;是一種統一的接口機制&#xff0c;用來處理所有不同的…

JAVA常量

2019獨角獸企業重金招聘Python工程師標準>>> 常量就是一個固定值。它們不需要計算&#xff0c;直接代表相應的值。 常量指不能改變的量。 在Java中用final標志&#xff0c;聲明方式和變量類似&#xff1a; final double PI 3.1415927; 雖然常量名也可以用小寫&…

基于Docker托管Azure DevOps代理

Azure DevOps非常好用&#xff0c;但是為代理準備單獨的服務器經常會顯得性價比不高&#xff1a;配置低了&#xff0c;前端構建時會教會你做人&#xff0c;配置太高又有點浪費資源&#xff0c;代理數量少了各團隊構建要打架。對于既想享受DevOps的美妙之處但是資源捉襟見肘的小…

微軟 word轉換pdf_如何將行轉換為Microsoft Word表中的列

微軟 word轉換pdfYou’ve created a table in Word and started to enter your data. Then, you realize that the table should be transposed, meaning the rows should be columns and vice versa. Rather than recreating the table and manually entering the data again,…

pycharm中如何正確配置pyqt5

網上看了幾個文章&#xff0c;不成功。這樣做才是正確姿勢&#xff1a; /Users/mac/anaconda3/bin/Designer.app /Users/mac/anaconda3/bin$ProjectFileDir$ pyuic5 $FileName$ -o $FileNameWithoutExtension$.py $ProjectFileDir$ 其它細節懶得說。 轉載于:https://www.cnblog…