一、前言
关于 ChatGPT 的论题,咱们都不陌生,咱们直入论题,由于 ChatGPT 现在约束中国拜访服务,所以假如直接运用 ChatGPT 网页进行对话,仍是不太便利。经过 ChatGPT SessionToken 就能够不约束网络拜访,所以咱们发挥想象力完结各种的聊天机器人、小程序,而原生 app 或许体验更好!所以就有了 iChatGPT!一款用 SwiftUI 完结的开源 ChatGPT app,欢迎咱们重视和提 PR。
二、iChatGPT
GitHub 开源地址:github.com/37iOS/iChat…
现在 v1.1.0,完结 ChatGPT 根本聊天功用:
- 能够直接与 ChatGPT 对话,并且保存上下文;
- 能够仿制问题和回答内容;
- 能够方便重复提问等
支持系统:
- iOS 14.0+
- iPadOS 14.0+
- macOS 11.0+
三、App 运用介绍
首先,需求点击 app 右上角图标,增加 ChatGPT SessionToken
密钥才干运用,不然无法恳求。
获取 SessionToken
的办法很多,比方抓网络恳求,其间阅读器办法最简略:
- 登录 chat.openai.com/chat
- 按 F12 翻开控制台(macOS 能够用方便键
command + option + I
) - 切换到 Application(应用) 选项卡,找到 Cookies (Safari 阅读器是
储存空间
选项卡) - 仿制
__Secure-next-auth.session-token
的值,增加到 app 后确认。
iOS 操作的界面:
macOS 操作界面:
四、App 完结介绍
运用 SwiftUI 大约几个小时就完结所有的作业,便利跟苹果生态完结。完结的难点就或许便是模拟 ChatGPT 恳求进程。现在是根据 A-kirami/nonebot-plugin-chatgpt 项目中的 python 完结,用 Swift 重写了一次,而 ChatGPT 登陆暂时没有完结,咱们能够提 pr。
最终封装的网络恳求类 ChatGPT.swift
class Chatbot {
let apUrl = "https://chat.openai.com/"
let sessionTokenKey = "__Secure-next-auth.session-token"
let timeout = 30
var sessionToken: String
var authorization = ""
var conversationId = ""
var parentId = ""
let id = ""
init(sessionToken: String) {
self.sessionToken = sessionToken
}
func headers() -> [String: String] {
return [
"Host": "chat.openai.com",
"Accept": "text/event-stream",
"Authorization": "Bearer \(self.authorization)",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
"X-Openai-Assistant-App-Id": "",
"Connection": "close",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://chat.openai.com/chat",
]
}
func getPayload(prompt: String) -> [String: Any] {
var body = [
"action": "next",
"messages": [
[
"id": "\(UUID().uuidString)",
"role": "user",
"content": ["content_type": "text", "parts": [prompt]],
]
],
"parent_message_id": "\(self.parentId)",
"model": "text-davinci-002-render",
] as [String: Any]
if !self.conversationId.isEmpty {
body["conversation_id"] = self.conversationId
}
return body
}
func refreshSession() async {
let cookies = "\(sessionTokenKey)=\(self.sessionToken)"
let url = self.apUrl + "api/auth/session"
let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
request.addValue(cookies, forHTTPHeaderField: "Cookie")
do {
let (data, response) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any] {
// Use the dictionary here
if let accessToken = dictionary["accessToken"] as? String {
authorization = accessToken
}
}
guard let response = response as? HTTPURLResponse,
let cookies = HTTPCookieStorage.shared.cookies(for: response.url!) else {
// handle error
print("改写会话失利: <r>HTTP:\(response)")
return
}
for cookie in cookies {
if cookie.name == sessionTokenKey {
self.sessionToken = cookie.value
UserDefaults.standard.set(cookie.value, forKey: ChatGPTSessionTokenKey)
}
}
}
catch {
print("改写会话失利: <r>HTTP:\(error)")
}
}
func getChatResponse(prompt: String) async -> String {
if self.authorization.isEmpty {
await refreshSession()
}
let url = self.apUrl + "backend-api/conversation"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers()
let dict = getPayload(prompt: prompt)
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
request.httpBody = jsonData
let (data, response) = try await URLSession.shared.data(for: request)
guard let response = response as? HTTPURLResponse else {
let err = "非预期的呼应内容: <r>HTTP:\(response)"
print(err)
return err
}
if response.statusCode == 429 {
return "恳求过多,请放慢速度"
}
guard let text = String(data: data, encoding: .utf8) else {
return "非预期的呼应内容: 内容读取失利~"
}
if response.statusCode != 200 {
let err = "非预期的呼应内容: <r>HTTP:\(response.statusCode)</r> \(text)"
print(err)
return err
}
let lines = text.components(separatedBy: "\n")
// 倒数第四行,第6个字符后开端
let str = lines[lines.count - 5]
#if DEBUG
print(str)
#endif
let jsonString = str.suffix(from: str.index(str.startIndex, offsetBy: 6))
guard let jsondata = jsonString.data(using: .utf8) else {
return ""
}
let json = try JSONSerialization.jsonObject(with: jsondata, options: [])
guard let dictionary = json as? [String: Any],
let conversation_id = dictionary["conversation_id"] as? String,
let message = dictionary["message"] as? [String: Any],
let parent_id = message["id"] as? String,
let content = message["content"] as? [String: Any],
let texts = content["parts"] as? [String],
let parts = texts.last
else {
return "解析错误~"
}
self.parentId = parent_id
self.conversationId = conversation_id
return parts
}
catch {
return "异常:\(error)"
}
}
}
唯一能够说说的便是,ChatGPT 的 backend-api/conversation
接口回来的内容,为了完结一个连接翻开的效果,回来了一堆的数据。例如一个回答是 "我无法确认全球当时的人口数量,由于我没有阅读网页的才能。"
,回来的内容是这样:
data: {"message": {"id": "xxxx", "role": "assistant", "user": null, "create_time": null, "update_time": null, "content": {"content_type": "text", "parts": ["我"]}, "end_turn": null, "weight": 1.0, "metadata": {}, "recipient": "all"}, "conversation_id": "xxxx", "error": null}
data: {"message": {"id": "xxxx", "role": "assistant", "user": null, "create_time": null, "update_time": null, "content": {"content_type": "text", "parts": ["我无"]}, "end_turn": null, "weight": 1.0, "metadata": {}, "recipient": "all"}, "conversation_id": "xxxx", "error": null}
data: {"message": {"id": "xxxx", "role": "assistant", "user": null, "create_time": null, "update_time": null, "content": {"content_type": "text", "parts": ["我无法"]}, "end_turn": null, "weight": 1.0, "metadata": {}, "recipient": "all"}, "conversation_id": "xxxx", "error": null}
中心省略xxxx行
中心省略xxxx行
中心省略xxxx行
data: {"message": {"id": "xxxx", "role": "assistant", "user": null, "create_time": null, "update_time": null, "content": {"content_type": "text", "parts": ["我无法确认全球当时的人口数量,由于我没有阅读网页的才能"]}, "end_turn": null, "weight": 1.0, "metadata": {}, "recipient": "all"}, "conversation_id": "xxxx", "error": null}
data: {"message": {"id": "xxxx", "role": "assistant", "user": null, "create_time": null, "update_time": null, "content": {"content_type": "text", "parts": ["我无法确认全球当时的人口数量,由于我没有阅读网页的才能。"]}, "end_turn": null, "weight": 1.0, "metadata": {}, "recipient": "all"}, "conversation_id": "xxxx", "error": null}
所以,需求按行切割,然后取倒数第四行的内容,再去掉 data:
字符才是咱们想要的 json 内容。
let lines = text.components(separatedBy: "\n")
// 倒数第四行,第6个字符后开端
let str = lines[lines.count - 5]
当然,现在 ChatGPT 仍是 beta 阶段,所以暂时没有开放 API,后续假如供给 API,就会更加便利!
五、ChatGPT 的一些问题
是否收费
现在 ChatGPT 是 beta 免费运用阶段,未来 API 恳求会收费,具体可参阅 openai.com/blog 。
修改头像
ChatGPT 对话的个人头像,咱们发现无法有 openai.com 上进行修改。由于现在运用的是 Gravatar 服务。
Gravatar,全称 Globally Recognized Avatar
。翻译成中文叫:全球通用头像。
Gravatar 的概念首先是在国外的独立 WordPress 博客中兴起的,当你到任何一个支持Gravatar的网站留言时,这个网站都会根据你所供给的Email地址为你显示出匹配的头像。当然,这个头像,是需求你事先到 Gravatar 的网站注册并上传的,不然,在这个网站上,就只会显示成一个默认的头像。
简略来说,便是头像链接为 https://s.gravatar.com/avatar/xxx
,其间 xxx
便是你登陆邮箱的 MD5 值,只要在 Gravatar 注册验证了这个邮箱,你就能够更新头像,或者任何人都能够获取你的头像,只要知道你的邮箱。详细能够参阅:Image Requests – Globally
有趣的对话
写一首诗,庆祝 iChatGPT app 开源:
咦,知道 iChatGPT
它酷炫极了,支持语言模型交互
它开源了,人人可用
快来下载,体验它的强壮
它能够帮助你,完结杂乱任务
不论是写文章,仍是做研讨
它是程序员的好帮手
让作业更高效,更愉快
啦啦啦,iChatGPT
开源了,万岁!
直呼牛~
六、总结
现在 iChatGPT 开源地址:github.com/37iOS/iChat… 。还有很多功用没有完结,比方:
- 保存对话
- 代码没有高亮
-
显示个人头像(v1.1.0 已完结) -
恳求失利重试等等(v1.1.0 已完结)
欢迎咱们提 PR !
别的,咱们近期也会更新 AppleParty,更新苹果批量上传内购产品功用,敬请等待~
最终,咱们觉得 ChatGPT
解决了什么痛点?有什么等待吗
欢迎咱们谈论区一起讨论沟通~
欢迎重视咱们,了解更多 iOS 和 Apple 的动态~
参阅引证
- 37iOS/iChatGPT – GitHub
- OpenAI ChatGPT
- OpenAI Blog
- A-kirami/nonebot-plugin-chatgpt
- Image Requests – Globally