This repository has been archived on 2026-01-07. You can view files and clone it, but cannot push or open issues or pull requests.
Files
docs/prompts.md
2024-08-29 19:01:54 +08:00

24 KiB
Raw Blame History

查看正在调试的 iPhone 的日志:

cd /Users/oscar/Downloads/brew-4.3.8/bin ./ios-deploy --bundle_id com.easyprompts.aigrammar --download=/Library/Caches/swiftybeaver.log --to ~/Downloads/ 对应目录下的 Library/Caches/swiftybeaver.log 既是

我有个Appstore IAP 的问题。用户A在 我们的app里面订阅了付费服务使用的是他自己的apple id 然后他在另外一台iPhone上登陆这个 apple id打开我们的应用之后点击 Restore Purchase这会触发appstore的回调操作吗 我该如何保证这个信息可以同步到我们的服务端?

好的明白了。那么现在我们在新的这台设备上没有得到新的订阅信息。我在app里使用了日志库来记录信息我应该怎么把这个日志文件读取出来

需要一个菊花转动,与后台交互

我们继续工作。先回忆一下我们之前都做了哪些。我们编写了GrammarCheckViewTranslateViewWordsView和SettingsView。他们分别完成不同的功能。现在我们需要一个统一的组建它需要在客户端有点击事件与服务端交互时弹出使得整个页面不可点击和操作等服务端结果出来之后再刷新页面。这个组建通常是一个转动的齿轮。它需要在我们上面提到的每个View中用到所以我们需要一个比较好的封装。请你以 WordsView 页面为例,给出实现的方法。

付费页面

quest1

我们继续在APP中增加付费功能。在首页也就是GrammarCheckView中我们有个"Try for Free" 按钮我们希望用户点击后能弹出一个付费界面。这个页面是全屏的并且会在其他页面上弹出比如SettingsView。所以我们希望它能够被多个地方引用。 这个页面的布局最上面左侧是一个VIP的图片标记可以用Text来包装背景是淡黄色文字是VIP金黄色。右侧是一个关闭按钮点击后关闭付费页面。 下面是个表格有四行三列。表头的三行分别是Features、Free、Premium。下面是一些功能的对比。 在下面是个VIP的商品列表这里可能有三个或者两个商品要展示的信息是商品名称、付费金额以及单价。它分为两列左边的主要字体是商品名称下面一行小字是付费金额右边的主要字体是单价。

最下面是一个购买的按钮,居中显示,点击后拉起应用商店的支付。 按钮下面可以加一行小字显示“Billed YearlyCancel Anytime.” 注意这个字会随着用户选择的商品变化而变化。

现在,我们可以先生成一下页面看看效果了。

quest2

单词页面

Q1

首先我们希望把搜索框和右边的Cancel按钮放到一个组件中让它看起来像个整体。为此我们在它们的背景上增加一个矩形框它与整体的背景颜色相同但会有比较细的边框用于和背景区分然后我们把文本框的底色设置跟背景色接近Cancel按钮只显示白色文字即可 其次我们现在用HStack封装了ListList是我们所需要的动态填充的数据。但现在的页面比较难看我们希望封装List的部分跟背景色完全重合这样从视觉上看会只有几个List的内容。 请你帮忙修改一下代码,然后我们观察一下效果。有问题可以问我。

SettingsView 页面

首先我们使用了List来展示几个功能但我不希望有header这一行我希望能够把Section的内容直接衔接到页面顶端。这应该如何设置 还是说我们可以更换其他组件来实现这个功能?

好的。我们继续来修改。 首先我希望这个ScrollView能撑满整个页面这样能保持背景颜色的一致性 其次,我们需要对每个功能增加一些效果: "Upgrade to Premium" ,我们在文字的左侧需要增加一个向上的箭头图标,用户点击这段文本,弹出我们之前写好的 VIPPaymentView "Feedback", 我们在文字的左侧同样加一个反馈的图标用户点击后调用appstore的反馈入口 "About",我们在文字的左侧也加一个图标,用户点击后,会弹出一个包含 Privacy Policy 和 User Terms 的页面; "Restore Pruchases",左侧增加一个向上的箭头图标,用户点击后,调用我们已经开发完成的 IapManager 中的 restorePurchases 方法。 现在,请你理解需求,并在上面的代码上完成修改。

Term of Use

我们的AIGrammar应用已经开发了一段时间了。为了准备把它上架到app store应用中我们需要准备两个文案其中一个是用户服务条款也就是 Term of Use。现在我们的应用会上架到除中国大陆以外的市场它主要会包含对英语文本的语法和拼写纠错翻译英文到中文的互相翻译英文单词的词典功能等。并且它会提供免费的基础服务以及付费订阅的高级服务。除此之外我们还希望用户可以使用他们的google或者apple账户来注册AIGrammar这样会更好的完善付费功能与历史数据保存等。基于以上的内容你可以用英语帮我写一篇Term of Use吗

好的,那能继续给我一个 Privacy Policy 吗?

NetworkManager

在 NetworkManager 的 checkGrammar 函数中它的header是这样的 let headers: HTTPHeaders = createAuthorizationHeader()

现在我们需要在headers中增加两个参数一个是 timezone 它是一个字符串,是用户所在的时区;另一个是 secondsfromgmt ,它表示用户所在市区与格林威治时间的差。 请正确获取这两个参数并把它拼接到headers中。 另外,考虑到我们可能有若干个接口都需要增加这两个参数,所以可以把他们封装成一个函数。

很好。你还记得我们统一封装的网络应答吧,它是这样的: struct APIResponse<T: Decodable>: Decodable { let ret: Int let message: String let data: T? } 现在我们有个问题。服务端的接口它的data字段直接是一个json格式的数组比如返回的是我们之前定义过的 GrammarRes 数组。 // 确保GrammarRes遵循Codable协议以支持JSON解析 struct GrammarRes: Codable { var plain: String var type: String var reason: String var correction: [String] }

那么,我们应该调用 performRequest 时,应该怎么传递 completion 部分?

我们根据之前编写的 NetWorkManager 类的代码在其中增加了一个函数它用来处理IAP的验证代码如下

// 查询VIP订单属于 NetWorkManager 类。
func IapVerify(receiptData: Data, appAccountToken: UUID?, productId: String, transactionId: UInt64, completion: @escaping (Result<IAPVerifyRsp, Error>) -> Void) {
    let url = globalEnvironment.iapVerifyURL
    
    let parameters: [String: Any] = ["transid":transactionId, "appaccounttoken": appAccountToken ?? "", "productid":productId, "receiptData":receiptData]
    let headers: HTTPHeaders = createAuthorizationHeader()
    
    performRequest(
        endpoint: url,
        parameters: parameters,
        method: .post,
        encoding: URLEncoding.httpBody,
        headers: headers,  // 示例使用JWT token
        completion: { (result: Result<IAPVerifyRsp, Error>) in
            switch result {
            case .success(let rsp):
                logger.info("verify succ: \(rsp.ret)")
                completion(.success(rsp))
            case .failure(let error):
                logger.error("Error: \(error.localizedDescription)")
                completion(.failure(error))
            }
        }
    )
}

现在,我们需要在 IapManager中完成 validateReceipt 函数,它功能是调用上面的 IapVerify并且获得其返回根据返回结果来控制一些界面的行为比如弹出成功页面等。现在请你完成代码。

现在我们调整一下它的输出。如果服务器端的http响应码是200那么它正确处理的数据格式如下 { "ret": 0, "message": "success", "data": { "id": 10002, "userid": "", "username": "", "vip": 0 } } 如果服务器端处理出错了,它会返回这样的格式: { "ret": 500, "message": "bad request", "data": nil } 现在我们需要按照这个格式处理数据。处理逻辑是如果服务器成功响应即返回了200状态码那么先解析ret字段如果ret为0那么继续匹配我们之前定义的结构体比如 struct VIPStatusResponse: Codable { var isvip: Int } 如果ret不为0需要按照出错来处理并不需要解析data字段。请调整你的代码。

class InitApp { static let shared = InitApp()

private let userDefaults = UserDefaults.standard
private let deviceIDKey = "DeviceID"

init() {
    initializeApp()
}

private func initializeApp() {
    // do something
    print("init")
}

}

这是一个用swift5编写的类。请问它的 shared 变量是什么作用? 如果我想在app启动时就进行这个类的初始化那应该怎么调用

我们创建 user.go 文件,它完成我们的用户相关的功能,并被 main.go 中的echo入口函数调用。第一个功能是当用户启动app时发送的查询请求 1函数名是 queryUserHandler 他读取 echo框架中的 请求参数,分别是 deviceid 和 userid类型为string 2我们使用 deviceid 查询 user表以deviceid匹配 user.DeviceID如果查询到记录则返回 IDUserIDUserName 如果查询不到则说明该用户不存在需要插入一条记录user.DeviceID=deviceid返回 ID 3我们用查询到的ID来查询 vip表如果查询到则返回 vip = vip.IsVIP, 如果查询不到,则 vip=0 4我们定义个json结构体作为函数的整体返回格式为 {"userid" : ID, "username": UserName, "vip": vip} 现在,请完成这部分的代码。

我们在使用swift5开发iOS应用。我们打算创建一个swift文件它里面有一个initAPP类用于执行在app启动时的各种初始化动作。包括 1获取 DeviceID。如果本地的 UserDefaults 中没有数据,那么需要调用 TrustDecision 来获取DeviceID如果 UserDefaults 中有,则直接使用; 2获取用户的VIP状态它需要发起一个网络访问我们使用 Alamofire 网络框架包,来发起查询;获取到的 VIP 状态,保存为一个内部的变量即可,这样我们每次启动都会查询一次;

现在,请你帮我写出 initAPP 的代码框架,并且完成初步的工作。

我们需要在里面增加一个函数,它的功能是从服务器端获取用户的付费状态: 1后端服务器的地址是 http://localhost:1080/user/queryvip 使用POST提交参数使用 x-www-form-urlencoded 编码。 2提交的参数user 是个字符串它是用户注册的用户名deviceID是设备ID它是一个字符串。 3服务器返回的结果如果 http code 为200表示正常返回body内容为 {"isvip":"%d"} 对应的%d就是我们要的结果其他http code 表示为异常; 4使用之前写的基于 Alamofire 和 SwiftJWT 的代码完成服务器访问注意要处理正常和异常的返回情况并且我们需要设定服务器访问的超时时间为10s 5我们在一个 initAPP 类的函数 queryVIP 中调用上面的函数,如果正确返回,则设置 initAPP 类中的 变量 vipStatus它是一个bool类型对应上面body中的 isvip 的值。 请结合已完成的NetworkManager代码实现上述功能。

好的,我们调整一下当前的实现: 1我们不需要使用echo框架因为在我们的主程序中已经包含了NewUserBenefits 只需要完成特定功能即可; 2我们需要修改返回内容明确区分用户是否有权益、权益已正常消耗以及其他的错误为此我们除了error还要增加一个返回值可以是 int返回 0 表示成功,其他值表示失败; 3我们的输入参数除了userID, timeZone之外还需要增加一个 secondsFromGMT 它是一个 int类型的值表示为 传入的时区,与格林威治时间的偏移; 4我们增加一下代码逻辑time.LoadLocation(timeZone) 这里增加一下错误处理如果失败了比如传入的timezone无法匹配等那么 我们用 secondsFromGMT 来计算传入的时区; 5我们需要增加一个 query 函数,它有一个输入参数 userID用于查询数据库中所有与之相关的key并以json格式的数据进行返回 6考虑到 redis 的处理性能问题,我们希望把当前的创建连接方法,改为有最大连接限制的连接池,每次查询时,从连接池中获取; 请你按照上面的修改意见,继续完善代码。

我在使用apple的 IAP功能接入storekit2在购买接口时这样传入 appAccountToken

        let uuid = Product.PurchaseOption.appAccountToken(UUID.init(uuidString: "xxxxx")!)
        
        let result = try await product.purchase(options: [uuid])

但在运行时报错了显示Fatal error: Unexpectedly found nil while unwrapping an Optional value 。这是为什么?如何才能正确的传入 appAccountToken 以及如何设置appAccountToken才能更好的完成交易

我在打印 StoreKit2 返回的交易结果时,期望能把交易的 jsonRepresentation 整体打印出来,但是用下面的方式: if let receiptData = try? transaction.jsonRepresentation { print("Transaction Receipt: (receiptData)") }

它的输出只是 Transaction Receipt: 769 bytes 。如何才能详细打印出 jsonRepresentation

很棒!我已经引入了 TrustDecision使用它来获取 DeviceID的相关代码是这样的

import TrustDecision

var options = String : NSObject let responseCallback: ([String : Any])-> Void = { response in // Response in sub-thread, do something with the response // Get DeviceId let deviceId = response["device_id"]
// Get DeviceRiskLabel let deviceRisk = response["device_risk_label"] // Get DeviceDetail let deviceDetail = response["device_detail"]
} options["callback"] = unsafeBitCast(responseCallback as @convention(block) ([String : Any]) -> Void, to: AnyObject.self) as? NSObject let manager = TDMobRiskManager.sharedManager() manager?.pointee.initWithOptions(options)

应用到我们的App中我希望在App启动时就调用以上的相关代码来获取DeviceID然后把这个DeviceID存储在一个公共的变量中使得每个功能模块都能很方便的使用它但不能修改它。请帮我实现以上代码。

作为提醒,我们回顾一下当前的代码结构: 一个AIGrammarApp view用来引用 AllTabView AllTabView实现了底部导航栏它有四个功能模块GrammarCheckView、WordsView、TranslateView、SettingsView

我们给之前写的代码加一些网络请求。当用户点击“赞”或者“踩”的按钮时我们发起一个Http请求它使用POST方法发送的参数有 product值为“trans”表示为翻译模块 input 和 output分别为该点击对应的原文和译文 res当动作为点赞时为“good”点踩时为“bad”。 发送之后可以不用等待应答,就返回到主应用中。这个请求的地址是 http://localhost:1080/grammar/feedback使用x-www-form-urlencoded提交参数。 请注意,我们希望使用 Alamofire 网络框架来实现它,并且希望请求的地址是写在配置文件中,而不是在代码里。这意味着我们可以在之前定义的 GlobalEnvironment 中加入这个变量。

很好。现在我们要继续在这个请求上加上鉴权。 我们在早些时候写完了生成DeviceID并把它存入全局变量的工作。我们为AF.request中加入jwt鉴权。参与鉴权的信息 jwt token secret 是一个变量我们可以放在GlobalEnvironment中它的值是your_jwt_secret jwt采用HS256加密payload 是: { "deviceID": "%s", "gid": "%s", "exp1": %d } 其中 deviceID 是我们之前取到的值gid 是用户ID目前保留为空 exp1是时间戳字段我们设置为当前的时间戳加一天。

现在我们需要把这个鉴权过程加上去并设置合适的http header重新发起请求。

SubmitTextEditor 的功能需求是,把用户输入的文本 inputText提交到后端的服务器上等服务器返回了结果之后解析结果并显示到界面上来。为此我们需要 1后端服务器的地址是 http://localhost:1080/grammar/translate 使用POST提交参数使用 x-www-form-urlencoded 编码。 2提交的参数input 的值是 $inputText也就是用户输入的内容 lang 的值我们先传入字符串 chs后面我们将处理它。 3服务器返回的结果如果 http code 为200表示正常返回body内容为 {"translation":"%s"} 对应的%s就是我们要的结果output其他http code 表示为异常; 4使用之前写的基于 Alamofire 和 SwiftJWT 的代码完成服务器访问注意要处理正常和异常的返回情况并且我们需要设定服务器访问的超时时间为10s 5当服务器正常返回时我们调用addTranslation此时需要把返回结果output传入如果是异常返回弹出toast提示用户“network errorplease retry later.” 请结合刚才的代码,实现上述功能。

TextField("word", text: $searchText, onCommit 这里,我们对 onCommit事件加上与服务器端的交互 1我们读取 searchText 的值,把它作为参数提交到服务端; 2后端服务器的地址是 http://localhost:1080/grammar/words 使用POST提交参数使用 x-www-form-urlencoded 编码。 3提交的参数input 的值是 $searchText也就是用户输入的内容 lang 的值我们先传入字符串 eng后面我们将处理它。 4服务器返回的结果如果 http code 为200表示正常返回body内容为 {"word":"$word", "explain":["$exp1", "exp2", ...], "phrase": ["$p1", "$p2", "p3", ...], "sync": ["$s1", "s2", ...]} ,我们需要解析结果,并把 explain, phrase, sync 分别赋值给 wordDefinitions, commonPhrases, synonyms。请注意我们可能需要修改这几个变量的声明方式。 5当 http code 为200 以外的值时表示为结果异常需要弹出toast提示用户“network errorplease retry later.” 6我们的网络交互部分可以在刚刚完成的 NetworkManager 中添加;这样 wordsview中只需要调用接口即可保持代码简洁。 现在,请你理解上面的需求,并输出相关的代码。

let wordDefinitions = ["Definition 1", "Definition 2", "Definition 3", "Definition 4", "Definition 5"]
let commonPhrases = ["Phrase 1", "Phrase 2", "Phrase 3"]
let synonyms = ["Synonym 1", "Synonym 2", "Synonym 3"]

很好。我们现在优化一下globalEnvironment它现在是这样写的

import Foundation import SwiftUI

class GlobalConfig : ObservableObject{ @Published var backgroundColor : UInt = 0xFFE4E1

}

class GlobalEnvironment: ObservableObject { @Published var deviceID: String = ""

var jwtSecret: String = "your_jwt_secret"

// 请求地址
var baseHost: String = "http://localhost:1080"
var feedbackURL: String = "http://192.168.2.2:1080/grammar/feedback"
var translateURL: String = "http://192.168.2.2:1080/grammar/translate"
var dictURL: String = "http://192.168.2.2:1080/grammar/words"
var grammarURL: String = "http://192.168.2.2:1080/grammar/grammar"

}

// 全局实例 let globalEnvironment = GlobalEnvironment()

我们可能有多个URL地址他们的基础路径可能是一样的比如域名端口等这些所以我们不希望每个地址变量都写一遍而是有一个baseHost定义公共的部分后续的URL都以此为基础加上自己的相对路径。现在请你帮我优化一下上面的写法。

我们要在 InputView 中的 Button("Check") 事件里,增加对服务器端的访问。整个过程是: 1我们读取textInput的值把它作为参数提交到服务端 2后端服务器的地址是 http://localhost:1080/grammar/grammar 使用POST提交参数使用 x-www-form-urlencoded 编码。 3提交的参数input 的值是 $textInput也就是用户输入的内容 lang 的值我们先传入字符串 eng后面我们将处理它。 4服务器返回的结果如果 http code 为200表示正常返回body内容格式为 [{"plain":"%s", "type":"%s", "reason":"%s", correction":["%s", "%s"]}, {"plain":"%s", "type":"%s", "reason":"%s", "correction":["%s", "%s"]}, ……] 这是一个json数组。我们需要解析它并赋值给 GrammarCheckView 中的 $results。它是一个数组格式为GrammarRes请注意为了适配解析我们可能要对 GrammarRes 的定义做调整。 5当我们处理完服务器交互之后可以继续 Button("Check") 里的几个现有的设置,这样,页面会切换到 ResultView显示结果。 6当 http code 为200 以外的值时表示为结果异常需要弹出toast提示用户“network errorplease retry later.” 7我们的网络交互部分可以在刚刚完成的 NetworkManager 中添加;这样 wordsview中只需要调用接口即可保持代码简洁。 现在,请你理解上面的需求,并输出相关的代码。

textContent 在 ResultView 中,我们有个函数 func styledText() -> Text ,它的作用是检查结果中的字符串,并且拼接出一个完整的输出。现在我们调整一下它的实现: 1我们的 outstr 默认为 textContent这意味着我们有完整的 textInput 2对results的每一次遍历我们可以用 res.plain 去匹配 outstr如果匹配的到那么用 getColoredText 转换之后的字符串,替换被匹配到的 outstr 中的子串; 3这样遍历完毕之后我们的到的 outstr是完整的 textInput并在每个results的输出部分被做了标注标注的方式是 getColoredText。 4请注意res.plain 去匹配 outstr 的时候,我们认为每个 res.plain 出现的顺序,与其在 textInput 中出现的顺序是匹配的;这意味着,如果 results 的第一个 res.plain 被匹配到了,那么第二个 res.plain 去匹配时,无需从 outstr的开头匹配只需从上一次匹配到的位置之后开始。

它报错了。我们换个实现: 1首先我们遍历 results ,使用 res.plain 去分隔 textContent使它成为一个数组这个数组不被 res.plain 命中的子串,使用 textContent 中的原文; 2被 res.plain 命中的子串,我们使用 getColoredText 转换之后的字符串,来替代它,存储到数组中; 3最后我们按顺序把整个数组拼接起来。 好了,重新实现一遍。

我们有一个字符串 textContent和一个由若干个textContent的子串组成的数组 results。这些子串在数组中的顺序与其在textContent中出现的顺序一致。现在我们的需求是把textContent中出现在results 中的子串,换成 getColoredText(str: res.plain, type: res.type, index: index) 的结果而其他不匹配的子串保持不变。请使用swift5写出你的代码。

举个例子: textContent = "This is a demo text with more complex grammar checking algorithm of the pro version. This app have been designed to help you to write correctly and appear more professional. I work really hard to make this app as god as possible. If you are happy with the results, please consider supporting the app by subscribing to the PRO plan. The text writen here is to show you how much the app is capable of with the pro version. is this something you would like to use?"

results = [{"plain":"This app have", "type":"grammar", "reason":"verb agreement error", "correction":["This app has"]}, {"plain":"god", "type":"spell", "reason":"spelling error", "correction":["good"]}, {"plain":"The text writen here", "type":"spell", "reason":"spelling error", "correction":["The text written here"]}, {"plain":"is this something", "type":"grammar", "reason":"start of sentence punctuation error", "correction":["Is this something"]}]