iOS开发 如何通过网络请求集成AI 实现和AI的聊天界面

一、以KimiAI举例,在用户中心界面申请自己的APIKey

d0b1b3abc89140e3acc74ea6fcca7e9e.png

二、根据官方提供的文档,创建几个结构体用来解析API响应和存储应用消息。

struct ChatCompletionResponse: Codable {
    let choices: [Choice]
    
    struct Choice: Codable {
        let message: APIMessage
    }
}

struct APIMessage: Codable {
    let role: String
    let content: String
}

struct AppMessage {
    let role: String
    let content: String
    let timestamp: Date
    let isIncoming: Bool
}

三、创建VC类 :

1.创建一个数组messages用来储存信息,再创建一个AVSpeechSynthesizer实例,用于语音合成。

    private var messages: [AppMessage] = []
    private let speechSynthesizer = AVSpeechSynthesizer()

2.用懒加载创建表格、文本试图和一个发送按钮。

    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ChatCell.self, forCellReuseIdentifier: "ChatCell")
        tableView.tableFooterView = UIView() // Hide extra separators
        tableView.estimatedRowHeight = 44
        tableView.rowHeight = UITableView.automaticDimension
        return tableView
    }()
    
    private lazy var messageInputView: UITextView = {
        let textView = UITextView()
        textView.layer.borderWidth = 1
        textView.layer.borderColor = UIColor.lightGray.cgColor
        textView.layer.cornerRadius = 5
        textView.translatesAutoresizingMaskIntoConstraints = false
        return textView
    }()
    
    private lazy var sendButton: UIButton = {
        let button = UIButton()
        button.setTitle("发送", for: .normal)
        button.backgroundColor = .blue
        button.layer.cornerRadius = 5
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleSend), for: .touchUpInside)
        return button
    }()

3.定义setupLayout方法,用于将表格视图、文本输入视图和发送按钮添加到视图中,并设置它们的布局约束。

    private func setupLayout() {
        view.addSubview(tableView)
        view.addSubview(messageInputView)
        view.addSubview(sendButton)
        
        NSLayoutConstraint.activate([
            // 省略了具体的约束代码
        ])
    }

4.处理发送的消息。

    @objc private func handleSend() {
        guard let text = messageInputView.text, !text.isEmpty else { return }
        let outgoingMessage = AppMessage(role: "User", content: text, timestamp: Date(), isIncoming: true)
        messages.append(outgoingMessage)
        tableView.reloadData()
        messageInputView.text = ""
        
        APIManager.shared.sendChatRequest(userInput: text) { result in
            switch result {
            case .success(let responseContent):
                let incomingMessage = AppMessage(role: "moonshot-v1-8k", content: responseContent, timestamp: Date(), isIncoming: false)
                DispatchQueue.main.async {
                    self.messages.append(incomingMessage)
                    self.tableView.reloadData()
                    
                    // 调用语音合成器读取返回的文本
                    self.speak(text: responseContent)
                }
            case .failure(let error):
                DispatchQueue.main.async {
                    print("请求错误: \(error.localizedDescription)")
                }
            }
        }
    }

5.将文本转化成语言播放。

    private func speak(text: String) {
        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") // 设置为中文(也可以改为其他语言)
        speechSynthesizer.speak(utterance)
    }

6.给TableView配置背景图片

    private func TableViewBackground() {
        let backgroundImage = UIImage(named: "backgroundimage")
        let backgroundImageView = UIImageView(image: backgroundImage)
        backgroundImageView.contentMode = .scaleAspectFill
        tableView.backgroundView = backgroundImageView
    }

7.根据message给表格视图提供数据源和行数。

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return messages.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChatCell", for: indexPath) as! ChatCell
        let message = messages[indexPath.row]
        cell.configure(with: message)
        return cell
    }

四、自定义ChatCell类

ChatCell类定义了一个自定义的聊天单元格,包含一个气泡视图bubbleView和一个消息标签messageLabelconfigure(with:)方法用于设置消息内容和样式。

class ChatCell: UITableViewCell {
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.font = UIFont.systemFont(ofSize: 16)
        label.lineBreakMode = .byWordWrapping
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let bubbleView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 15
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(bubbleView)
        bubbleView.addSubview(messageLabel)
        
        NSLayoutConstraint.activate([
            bubbleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            bubbleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            bubbleView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15),
            bubbleView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
            
            messageLabel.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 10),
            messageLabel.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -10),
            messageLabel.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 15),
            messageLabel.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -15)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configure(with message: AppMessage) {
        messageLabel.text = message.content
        messageLabel.textAlignment = message.isIncoming ? .left : .right
        bubbleView.backgroundColor = message.isIncoming ? UIColor.lightGray.withAlphaComponent(0.2) : UIColor.green.withAlphaComponent(0.7)
    }
}

五、处理API请求,将用户的输入发送到服务器并处理相应,这一部分需要参考API的官方文档

class APIManager {
    
    static let shared = APIManager()
    
    private let apiURL = "https://api.moonshot.cn/v1/chat/completions" 
    private let apiKey = "sk-SCqR38PT6Sr8qW9wcF2AvdgOCNi8jYcf7NeELSifdgNJtmFL"
    
    private init() {}
    
    func sendChatRequest(userInput: String, completion: @escaping (Result<String, Error>) -> Void) {
        guard let url = URL(string: apiURL) else {
            completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
            return
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let requestBody: [String: Any] = [
            "model": "moonshot-v1-8k",
            "messages": [
                [
                    "role": "system",
                    "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手。"
                ],
                [
                    "role": "user",
                    "content": userInput
                ]
            ],
            "temperature": 0.3
        ]
        
        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
        } catch {
            completion(.failure(error))
            return
        }
        
        let session = URLSession.shared
        let task = session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                let statusCodeError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Server error or invalid response"])
                completion(.failure(statusCodeError))
                return
            }
            
            guard let data = data else {
                let noDataError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"])
                completion(.failure(noDataError))
                return
            }
            
            do {
                let chatResponse = try JSONDecoder().decode(ChatCompletionResponse.self, from: data)
                let messageContent = chatResponse.choices.first?.message.content ?? "No content"
                completion(.success(messageContent))
            } catch {
                completion(.failure(error))
            }
        }
        
        task.resume()
    }
}

完整代码如下:

import UIKit
import Foundation
import AVFoundation

struct ChatCompletionResponse: Codable {
    let choices: [Choice]
    
    struct Choice: Codable {
        let message: APIMessage
    }
}

struct APIMessage: Codable {
    let role: String
    let content: String
}

struct AppMessage {
    let role: String
    let content: String
    let timestamp: Date
    let isIncoming: Bool
}

class ViewController2: UIViewController, UITableViewDelegate, UITableViewDataSource {

    private var messages: [AppMessage] = []
    private let speechSynthesizer = AVSpeechSynthesizer() // 创建语音合成器实例
    
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ChatCell.self, forCellReuseIdentifier: "ChatCell")
        tableView.tableFooterView = UIView() // Hide extra separators
        tableView.estimatedRowHeight = 44
        tableView.rowHeight = UITableView.automaticDimension
        return tableView
    }()
    
    private lazy var messageInputView: UITextView = {
        let textView = UITextView()
        textView.layer.borderWidth = 1
        textView.layer.borderColor = UIColor.lightGray.cgColor
        textView.layer.cornerRadius = 5
        textView.translatesAutoresizingMaskIntoConstraints = false
        return textView
    }()
    
    private lazy var sendButton: UIButton = {
        let button = UIButton()
        button.setTitle("发送", for: .normal)
        button.backgroundColor = .blue
        button.layer.cornerRadius = 5
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(handleSend), for: .touchUpInside)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Chat"
        view.backgroundColor = .gray
        setupLayout()
        TableViewBackground()
    }
    
    private func TableViewBackground() {
        let backgroundImage = UIImage(named: "backgroundimage")
        let backgroundImageView = UIImageView(image: backgroundImage)
        backgroundImageView.contentMode = .scaleAspectFill
        tableView.backgroundView = backgroundImageView
    }
    
    private func setupLayout() {
        view.addSubview(tableView)
        view.addSubview(messageInputView)
        view.addSubview(sendButton)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: messageInputView.topAnchor),
            
            messageInputView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            messageInputView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10),
            messageInputView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),
            messageInputView.heightAnchor.constraint(equalToConstant: 40),
            
            sendButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            sendButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10),
            sendButton.widthAnchor.constraint(equalToConstant: 60),
            sendButton.heightAnchor.constraint(equalToConstant: 40),
        ])
    }
    
    @objc private func handleSend() {
        guard let text = messageInputView.text, !text.isEmpty else { return }
        let outgoingMessage = AppMessage(role: "User", content: text, timestamp: Date(), isIncoming: true)
        messages.append(outgoingMessage)
        tableView.reloadData()
        messageInputView.text = ""
        
        APIManager.shared.sendChatRequest(userInput: text) { result in
            switch result {
            case .success(let responseContent):
                let incomingMessage = AppMessage(role: "moonshot-v1-8k", content: responseContent, timestamp: Date(), isIncoming: false)
                DispatchQueue.main.async {
                    self.messages.append(incomingMessage)
                    self.tableView.reloadData()
                    
                    // 调用语音合成器读取返回的文本
                    self.speak(text: responseContent)
                }
            case .failure(let error):
                DispatchQueue.main.async {
                    print("请求错误: \(error.localizedDescription)")
                }
            }
        }
    }
    
    // 语音合成
    private func speak(text: String) {
        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") // 设置为中文,也可以改为其他语言
        speechSynthesizer.speak(utterance)
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return messages.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChatCell", for: indexPath) as! ChatCell
        let message = messages[indexPath.row]
        cell.configure(with: message)
        return cell
    }
}

class ChatCell: UITableViewCell {
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.font = UIFont.systemFont(ofSize: 16)
        label.lineBreakMode = .byWordWrapping
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let bubbleView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 15
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(bubbleView)
        bubbleView.addSubview(messageLabel)
        
        NSLayoutConstraint.activate([
            bubbleView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            bubbleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
            bubbleView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 15),
            bubbleView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -15),
            
            messageLabel.topAnchor.constraint(equalTo: bubbleView.topAnchor, constant: 10),
            messageLabel.bottomAnchor.constraint(equalTo: bubbleView.bottomAnchor, constant: -10),
            messageLabel.leadingAnchor.constraint(equalTo: bubbleView.leadingAnchor, constant: 15),
            messageLabel.trailingAnchor.constraint(equalTo: bubbleView.trailingAnchor, constant: -15)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configure(with message: AppMessage) {
        messageLabel.text = message.content
        messageLabel.textAlignment = message.isIncoming ? .left : .right
        bubbleView.backgroundColor = message.isIncoming ? UIColor.lightGray.withAlphaComponent(0.2) : UIColor.green.withAlphaComponent(0.7)
    }
}

class APIManager {
    
    static let shared = APIManager()
    
    private let apiURL = "https://api.moonshot.cn/v1/chat/completions"
    private let apiKey = "sk-SCqR38PT6Sr8qW9wcF2AvdgOCNi8jYcf7NeELSifdgNJtmFL"
    
    private init() {}
    
    //处理用户输入
    func sendChatRequest(userInput: String, completion: @escaping (Result<String, Error>) -> Void) {
        guard let url = URL(string: apiURL) else {
            completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
            return
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let requestBody: [String: Any] = [
            "model": "moonshot-v1-8k",
            "messages": [
                [
                    "role": "system",
                    "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手。"
                ],
                [
                    "role": "user",
                    "content": userInput
                ]
            ],
            "temperature": 0.3
        ]
        
        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
        } catch {
            completion(.failure(error))
            return
        }
        
        let session = URLSession.shared
        let task = session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                let statusCodeError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Server error or invalid response"])
                completion(.failure(statusCodeError))
                return
            }
            
            guard let data = data else {
                let noDataError = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"])
                completion(.failure(noDataError))
                return
            }
            
            do {
                let chatResponse = try JSONDecoder().decode(ChatCompletionResponse.self, from: data)
                let messageContent = chatResponse.choices.first?.message.content ?? "No content"
                completion(.success(messageContent))
            } catch {
                completion(.failure(error))
            }
        }
        
        task.resume()
    }
    
    
}

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值