Sfoglia il codice sorgente

[feat]ios版本键盘1.0

Destiny 11 mesi fa
parent
commit
843db8a8fe
100 ha cambiato i file con 4869 aggiunte e 0 eliminazioni
  1. 11 0
      apple-app-site-association
  2. 8 0
      ios/AiKeyboard/123123.m
  3. 1 0
      ios/AiKeyboard/Animate/heart.json
  4. 1 0
      ios/AiKeyboard/Animate/keyboard_dialog_loading.json
  5. 1 0
      ios/AiKeyboard/Animate/keyboard_loading.json
  6. 1 0
      ios/AiKeyboard/Animate/vip_button.json
  7. 31 0
      ios/AiKeyboard/Extension/String+Extension.swift
  8. 104 0
      ios/AiKeyboard/Extension/UIButton+Extension.swift
  9. 99 0
      ios/AiKeyboard/Extension/UIColor+Extension.swift
  10. 103 0
      ios/AiKeyboard/Extension/UIImage+Extension.swift
  11. 38 0
      ios/AiKeyboard/Extension/UILabel+Extension.swift
  12. 235 0
      ios/AiKeyboard/Extension/UIView+Extension.swift
  13. 35 0
      ios/AiKeyboard/Info.plist
  14. 883 0
      ios/AiKeyboard/KeyboardViewController.swift
  15. 21 0
      ios/AiKeyboard/Marco/KeyboardConst.swift
  16. 6 0
      ios/AiKeyboard/Media.xcassets/Contents.json
  17. 22 0
      ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/Contents.json
  18. BIN
      ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/icon_arrow_down@2x.png
  19. BIN
      ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/icon_arrow_down@3x.png
  20. 22 0
      ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/Contents.json
  21. BIN
      ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/icon_arrow_up@2x.png
  22. BIN
      ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/icon_arrow_up@3x.png
  23. 22 0
      ios/AiKeyboard/Media.xcassets/icon_plus.imageset/Contents.json
  24. BIN
      ios/AiKeyboard/Media.xcassets/icon_plus.imageset/icon_plus@2x.png
  25. BIN
      ios/AiKeyboard/Media.xcassets/icon_plus.imageset/icon_plus@3x.png
  26. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/Contents.json
  27. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/keyboard_back_btn@2x.png
  28. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/keyboard_back_btn@3x.png
  29. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/Contents.json
  30. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/keyboard_bg@2x.png
  31. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/keyboard_bg@3x.png
  32. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/Contents.json
  33. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/keyboard_copy_icon@2x.png
  34. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/keyboard_copy_icon@3x.png
  35. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/Contents.json
  36. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/keyboard_exchange_btn@2x.png
  37. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/keyboard_exchange_btn@3x.png
  38. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/Contents.json
  39. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/keyboard_exchange_normal@2x.png
  40. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/keyboard_exchange_normal@3x.png
  41. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/Contents.json
  42. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/keyboard_func_select_bg@2x.png
  43. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/keyboard_func_select_bg@3x.png
  44. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/Contents.json
  45. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/keyboard_heart_btn@2x.png
  46. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/keyboard_heart_btn@3x.png
  47. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/Contents.json
  48. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/keyboard_logo@2x.png
  49. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/keyboard_logo@3x.png
  50. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/Contents.json
  51. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/keyboard_menu_button@2x.png
  52. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/keyboard_menu_button@3x.png
  53. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/Contents.json
  54. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/keyboard_menu_diy@2x.png
  55. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/keyboard_menu_diy@3x.png
  56. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/Contents.json
  57. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/keyboard_menu_market@2x.png
  58. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/keyboard_menu_market@3x.png
  59. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/Contents.json
  60. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/keyboard_menu_vip@2x.png
  61. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/keyboard_menu_vip@3x.png
  62. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/Contents.json
  63. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/keyboard_normal_btn_bg@2x.png
  64. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/keyboard_normal_btn_bg@3x.png
  65. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/Contents.json
  66. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/keyboard_paste_btn_bg@2x.png
  67. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/keyboard_paste_btn_bg@3x.png
  68. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/Contents.json
  69. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/keyboard_text_clear_btn@2x.png
  70. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/keyboard_text_clear_btn@3x.png
  71. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/Contents.json
  72. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/keyboard_user_change_icon@2x.png
  73. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/keyboard_user_change_icon@3x.png
  74. 22 0
      ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/Contents.json
  75. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/keyboard_vip_top@2x.png
  76. BIN
      ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/keyboard_vip_top@3x.png
  77. 49 0
      ios/AiKeyboard/Model/CharacterModel.swift
  78. 34 0
      ios/AiKeyboard/Model/ChatModel.swift
  79. 47 0
      ios/AiKeyboard/Model/KeyboardModel.swift
  80. 36 0
      ios/AiKeyboard/Model/PrologueModel.swift
  81. 76 0
      ios/AiKeyboard/Model/UserModel.swift
  82. 167 0
      ios/AiKeyboard/Network/KeyboardApi.swift
  83. 24 0
      ios/AiKeyboard/Network/KeyboardBaseResponse.swift
  84. 124 0
      ios/AiKeyboard/Network/KeyboardNetworkManager.swift
  85. 115 0
      ios/AiKeyboard/View/Cell/KeyboardCharacterCell.swift
  86. 103 0
      ios/AiKeyboard/View/Cell/KeyboardExchangeCell.swift
  87. 123 0
      ios/AiKeyboard/View/Cell/KeyboardTeachDialogueCell.swift
  88. 255 0
      ios/AiKeyboard/View/MainView/KeyboardBaseView.swift
  89. 127 0
      ios/AiKeyboard/View/MainView/KeyboardHelpView.swift
  90. 422 0
      ios/AiKeyboard/View/MainView/KeyboardPrologueView.swift
  91. 342 0
      ios/AiKeyboard/View/MainView/KeyboardTeachView.swift
  92. 176 0
      ios/AiKeyboard/View/PopView/KeyboardExchangeView.swift
  93. 244 0
      ios/AiKeyboard/View/PopView/KeyboardFunctionPopView.swift
  94. 105 0
      ios/AiKeyboard/View/PopView/KeyboardLoginTipView.swift
  95. 127 0
      ios/AiKeyboard/View/PopView/KeyboardMenuView.swift
  96. 153 0
      ios/AiKeyboard/View/PopView/KeyboardVipTipView.swift
  97. BIN
      ios/AiKeyboard/淘宝买菜体.ttf
  98. 1 0
      ios/Flutter/Debug.xcconfig
  99. 1 0
      ios/Flutter/Release.xcconfig
  100. 0 0
      ios/KeyboardSharedDataManager.swift

+ 11 - 0
apple-app-site-association

@@ -0,0 +1,11 @@
+{
+    "applinks": {
+        "apps": [],
+        "details": [
+            {
+                "appID": "S3Y6HGPL2S.com.xingmeng.xiaoting",
+                "paths": [ "/record/*" ]
+            },
+        ]
+    }
+}

+ 8 - 0
ios/AiKeyboard/123123.m

@@ -0,0 +1,8 @@
+//
+//  123123.m
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+#import <Foundation/Foundation.h>

File diff suppressed because it is too large
+ 1 - 0
ios/AiKeyboard/Animate/heart.json


File diff suppressed because it is too large
+ 1 - 0
ios/AiKeyboard/Animate/keyboard_dialog_loading.json


File diff suppressed because it is too large
+ 1 - 0
ios/AiKeyboard/Animate/keyboard_loading.json


File diff suppressed because it is too large
+ 1 - 0
ios/AiKeyboard/Animate/vip_button.json


+ 31 - 0
ios/AiKeyboard/Extension/String+Extension.swift

@@ -0,0 +1,31 @@
+//
+//  String+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import Foundation
+import UIKit
+
+extension String {
+    
+    public var isBlank: Bool {
+        return self.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) == ""
+    }
+    
+    public func heightAccording(width: CGFloat, height: CGFloat = CGFloat(MAXFLOAT), font: UIFont, lineSpacing: CGFloat) -> CGFloat {
+        if self.isBlank {return 0}
+        let rect = CGRect(x: 0, y: 0, width: width, height: height)
+        let label = UILabel(frame: rect)
+        label.font = font
+        label.text = self
+        label.numberOfLines = 0
+        let attrStr = NSMutableAttributedString(string: self)
+        let paragraphStyle = NSMutableParagraphStyle()
+        paragraphStyle.lineSpacing = lineSpacing
+        attrStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, self.count))
+        label.attributedText = attrStr
+        return label.sizeThatFits(rect.size).height
+    }
+}

+ 104 - 0
ios/AiKeyboard/Extension/UIButton+Extension.swift

@@ -0,0 +1,104 @@
+//
+//  UIButton+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import Foundation
+import UIKit
+
+// MARK: - UIButton 图片 与 title 位置关系
+extension UIButton {
+    
+    /// 图片 和 title 的布局样式
+    enum ImageTitleLayout {
+        case imgTop
+        case imgBottom
+        case imgLeft
+        case imgRight
+    }
+    
+    // MARK: 3.1、设置图片和 title 的位置关系(提示:title和image要在设置布局关系之前设置)
+    /// 设置图片和 title 的位置关系(提示:title和image要在设置布局关系之前设置)
+    /// - Parameters:
+    ///   - layout: 布局
+    ///   - spacing: 间距
+    /// - Returns: 返回自身
+    @discardableResult
+    func setImageTitleLayout(_ layout: ImageTitleLayout, spacing: CGFloat = 0) -> UIButton {
+        switch layout {
+        case .imgLeft:
+            alignHorizontal(spacing: spacing, imageFirst: true)
+        case .imgRight:
+            alignHorizontal(spacing: spacing, imageFirst: false)
+        case .imgTop:
+            alignVertical(spacing: spacing, imageTop: true)
+        case .imgBottom:
+            alignVertical(spacing: spacing, imageTop: false)
+        }
+        return self
+    }
+    
+    /// 水平方向
+    /// - Parameters:
+    ///   - spacing: 间距
+    ///   - imageFirst: 图片是否优先
+    private func alignHorizontal(spacing: CGFloat, imageFirst: Bool) {
+        let edgeOffset = spacing / 2
+        imageEdgeInsets = UIEdgeInsets(top: 0, left: -edgeOffset,
+                                            bottom: 0,right: edgeOffset)
+        titleEdgeInsets = UIEdgeInsets(top: 0, left: edgeOffset,
+                                            bottom: 0, right: -edgeOffset)
+        if !imageFirst {
+            transform = CGAffineTransform(scaleX: -1, y: 1)
+            imageView?.transform = CGAffineTransform(scaleX: -1, y: 1)
+            titleLabel?.transform = CGAffineTransform(scaleX: -1, y: 1)
+        }
+        contentEdgeInsets = UIEdgeInsets(top: 0, left: edgeOffset, bottom: 0, right: edgeOffset)
+    }
+    
+    /// 垂直方向
+    /// - Parameters:
+    ///   - spacing: 间距
+    ///   - imageTop: 图片是不是在顶部
+    private func alignVertical(spacing: CGFloat, imageTop: Bool) {
+        
+        guard let imageWidth = self.imageView?.kb_width,
+              let imageHeight = self.imageView?.kb_height,
+              let text = self.titleLabel?.text,
+              let font = self.titleLabel?.font
+        else {
+            return
+        }
+        
+        let labelString = NSString(string: text)
+        let titleSize = labelString.size(withAttributes: [NSAttributedString.Key.font: font])
+        let titleHeight = titleSize.height
+        let titleWidth = titleSize.width
+        let insetAmount = spacing / 2
+        
+        if imageTop {
+            
+            imageEdgeInsets = UIEdgeInsets(top: -titleHeight - insetAmount,
+                                           left: (self.kb_width - imageWidth) / 2,
+                                           bottom: 0,
+                                           right: (self.kb_width - imageWidth) / 2 - titleWidth)
+            titleEdgeInsets = UIEdgeInsets(top: 0,
+                                           left: -imageWidth,
+                                           bottom: -imageHeight - insetAmount,
+                                           right: 0)
+        } else {
+            
+            imageEdgeInsets = UIEdgeInsets(top: 0,
+                                           left: (self.kb_width - imageWidth) / 2,
+                                           bottom: -titleHeight - insetAmount,
+                                           right: (self.kb_width - imageWidth) / 2 - titleWidth)
+            titleEdgeInsets = UIEdgeInsets(top: -imageHeight - insetAmount,
+                                           left: -imageWidth,
+                                           bottom: 0,
+                                           right: 0)
+        }
+    }
+    
+}

+ 99 - 0
ios/AiKeyboard/Extension/UIColor+Extension.swift

@@ -0,0 +1,99 @@
+//
+//  UIColor+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import UIKit
+
+// MARK: - 二、使用方法设置颜色
+public extension UIColor {
+    
+    // MARK: 2.1、根据RGBA的颜色(方法)
+    /// 根据RGBA的颜色(方法)
+    /// - Parameters:
+    ///   - r: red 颜色值
+    ///   - g: green颜色值
+    ///   - b: blue颜色值
+    ///   - alpha: 透明度
+    /// - Returns: 返回 UIColor
+    static func color(r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat = 1.0) -> UIColor {
+        return UIColor(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: alpha)
+    }
+    
+    // MARK: 2.2、十六进制字符串设置颜色(方法)
+    static func hexStringColor(hexString: String, alpha: CGFloat = 1.0) -> UIColor {
+        let newColor = hexStringToColorRGB(hexString: hexString)
+        guard let r = newColor.r, let g = newColor.g, let b = newColor.b else {
+            assert(false, "颜色值有误")
+            return .white
+        }
+        return color(r: r, g: g, b: b, alpha: alpha)
+    }
+    
+    // MARK: 2.3、十六进制 Int 颜色的使用(方法)
+    /// 十六进制颜色的使用
+    /// - Parameters:
+    ///   - color: 16进制 Int 颜色 0x999999
+    ///   - alpha: 透明度
+    /// - Returns: 返回一个 UIColor
+    static func hexIntColor(hexInt: Int, alpha: CGFloat = 1) -> UIColor {
+        let redComponet: Float = Float(hexInt >> 16)
+        let greenComponent: Float = Float((hexInt & 0xFF00) >> 8)
+        let blueComponent: Float = Float(hexInt & 0xFF)
+        return UIColor(red: CGFloat(redComponet / 255.0), green: CGFloat(greenComponent / 255.0), blue: CGFloat(blueComponent / 255.0), alpha: alpha)
+    }
+}
+
+private extension UIColor {
+    
+    // MARK: 3.1、根据 十六进制字符串 颜色获取 RGB,如:#3CB371 或者 ##3CB371 -> 60,179,113
+    /// 根据 十六进制字符串 颜色获取 RGB
+    /// - Parameter hexString: 十六进制颜色的字符串,如:#3CB371 或者 ##3CB371 -> 60,179,113
+    /// - Returns: 返回 RGB
+    static func hexStringToColorRGB(hexString: String) -> (r: CGFloat?, g: CGFloat?, b: CGFloat?) {
+        // 1、判断字符串的长度是否符合
+        guard hexString.count >= 6 else {
+            return (nil, nil, nil)
+        }
+        // 2、将字符串转成大写
+        var tempHex = hexString.uppercased()
+        // 检查字符串是否拥有特定前缀
+        // hasPrefix(prefix: String)
+        // 检查字符串是否拥有特定后缀。
+        // hasSuffix(suffix: String)
+        // 3、判断开头: 0x/#/##
+        if tempHex.hasPrefix("0x") || tempHex.hasPrefix("0X") || tempHex.hasPrefix("##") {
+            tempHex = String(tempHex[tempHex.index(tempHex.startIndex, offsetBy: 2)..<tempHex.endIndex])
+        }
+        if tempHex.hasPrefix("#") {
+            tempHex = String(tempHex[tempHex.index(tempHex.startIndex, offsetBy: 1)..<tempHex.endIndex])
+        }
+        // 4、分别取出 RGB
+        // FF --> 255
+        var range = NSRange(location: 0, length: 2)
+        let rHex = (tempHex as NSString).substring(with: range)
+        range.location = 2
+        let gHex = (tempHex as NSString).substring(with: range)
+        range.location = 4
+        let bHex = (tempHex as NSString).substring(with: range)
+        // 5、将十六进制转成 255 的数字
+        var r: UInt64 = 0, g: UInt64 = 0, b: UInt64 = 0
+        Scanner(string: rHex).scanHexInt64(&r)
+        Scanner(string: gHex).scanHexInt64(&g)
+        Scanner(string: bHex).scanHexInt64(&b)
+        return (r: CGFloat(r), g: CGFloat(g), b: CGFloat(b))
+    }
+    
+    // MARK: 3.2、根据 十六进制值 颜色获取 RGB, 如:0x3CB371 -> 60,179,113
+    /// 根据 十六进制值 颜色获取 RGB, 如:0x3CB371 -> 60,179,113
+    /// - Parameter hexInt: 十六进制值,如:0x3CB37
+    /// - Returns: 返回 RGB
+    static func hexIntToColorRGB(hexInt: Int) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
+        let red: CGFloat = CGFloat(hexInt >> 16)
+        let green: CGFloat = CGFloat((hexInt & 0xFF00) >> 8)
+        let blue: CGFloat = CGFloat(hexInt & 0xFF)
+        return (red, green, blue)
+    }
+}

+ 103 - 0
ios/AiKeyboard/Extension/UIImage+Extension.swift

@@ -0,0 +1,103 @@
+//
+//  UIImage+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import Foundation
+import UIKit
+
+public enum ImageGradientDirection {
+    /// 水平从左到右
+    case horizontal
+    /// 垂直从上到下
+    case vertical
+    /// 左上到右下
+    case leftOblique
+    /// 右上到左下
+    case rightOblique
+    /// 自定义
+    case other(CGPoint, CGPoint)
+    
+    public func point(size: CGSize) -> (CGPoint, CGPoint) {
+        switch self {
+        case .horizontal:
+            return (CGPoint(x: 0, y: 0), CGPoint(x: size.width, y: 0))
+        case .vertical:
+            return (CGPoint(x: 0, y: 0), CGPoint(x: 0, y: size.height))
+        case .leftOblique:
+            return (CGPoint(x: 0, y: 0), CGPoint(x: size.width, y: size.height))
+        case .rightOblique:
+            return (CGPoint(x: size.width, y: 0), CGPoint(x: 0, y: size.height))
+        case .other(let stat, let end):
+            return (stat, end)
+        }
+    }
+}
+
+extension UIImage {
+    
+    static func image(color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) -> UIImage? {
+        return image(color: color, size: size, corners: .allCorners, radius: 0)
+    }
+
+    static func image(color: UIColor, size: CGSize, corners: UIRectCorner, radius: CGFloat) -> UIImage? {
+        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
+        // 防止size:(0, 0)崩溃
+        var drawSize = size
+        if drawSize.width <= 0 || drawSize.height <= 0 {
+            drawSize = CGSize(width: 1, height: 1)
+        }
+        UIGraphicsBeginImageContextWithOptions(drawSize, false, UIScreen.main.scale)
+        let context = UIGraphicsGetCurrentContext()
+        if radius > 0 {
+            let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+            color.setFill()
+            path.fill()
+        } else {
+            context?.setFillColor(color.cgColor)
+            context?.fill(rect)
+        }
+        let img = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        return img
+    }
+    
+    static func gradient(_ colors: [UIColor], size: CGSize = CGSize(width: 10, height: 10), locations:[CGFloat]? = nil, direction: ImageGradientDirection = .horizontal) -> UIImage? {
+        return gradient(colors, size: size, radius: 0, locations: locations, direction: direction)
+    }
+    
+    // MARK: 2.5、生成带圆角渐变色的图片 [UIColor, UIColor, UIColor]
+    /// 生成带圆角渐变色的图片 [UIColor, UIColor, UIColor]
+    /// - Parameters:
+    ///   - colors: UIColor 数组
+    ///   - size: 图片大小
+    ///   - radius: 圆角
+    ///   - locations: locations 数组
+    ///   - direction: 渐变的方向
+    /// - Returns: 带圆角的渐变的图片
+    static func gradient(_ colors: [UIColor],
+                         size: CGSize = CGSize(width: 10, height: 10),
+                         radius: CGFloat,
+                         locations:[CGFloat]? = nil,
+                         direction: ImageGradientDirection = .horizontal) -> UIImage? {
+        if colors.count == 0 { return nil }
+        if colors.count == 1 {
+            return image(color: colors[0])
+        }
+        UIGraphicsBeginImageContext(size)
+        let context = UIGraphicsGetCurrentContext()
+        let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: size.width, height: size.height), cornerRadius: radius)
+        path.addClip()
+        context?.addPath(path.cgPath)
+        guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors.map{$0.cgColor} as CFArray, locations: locations?.map { CGFloat($0) }) else { return nil
+        }
+        let directionPoint = direction.point(size: size)
+        context?.drawLinearGradient(gradient, start: directionPoint.0, end: directionPoint.1, options: .drawsBeforeStartLocation)
+        
+        let image = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        return image
+    }
+}

+ 38 - 0
ios/AiKeyboard/Extension/UILabel+Extension.swift

@@ -0,0 +1,38 @@
+//
+//  UILabel+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import UIKit
+
+//设置行间距
+extension UILabel {
+
+    /// 设置UILabel的行间距
+    /// - Parameter spacing: 行间距的大小
+    func setLineSpacing(_ spacing: CGFloat, alignment: NSTextAlignment = .left) {
+        guard let labelText = self.text else { return }
+
+        let paragraphStyle = NSMutableParagraphStyle()
+        paragraphStyle.lineSpacing = spacing
+        paragraphStyle.alignment = alignment
+
+        let attributedString: NSMutableAttributedString
+        if let labelAttributedText = self.attributedText {
+            attributedString = NSMutableAttributedString(attributedString: labelAttributedText)
+        } else {
+            attributedString = NSMutableAttributedString(string: labelText)
+        }
+
+        // 添加行间距属性
+        attributedString.addAttribute(
+            .paragraphStyle,
+            value: paragraphStyle,
+            range: NSRange(location: 0, length: attributedString.length)
+        )
+
+        self.attributedText = attributedString
+    }
+}

+ 235 - 0
ios/AiKeyboard/Extension/UIView+Extension.swift

@@ -0,0 +1,235 @@
+//
+//  UIView+Extension.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import Foundation
+import UIKit
+import Toast_Swift
+
+// MARK: - UIView 有关 Frame 的扩展
+public extension UIView {
+    // MARK: 3.1、x 的位置
+    /// x 的位置
+    var kb_x: CGFloat {
+        get {
+            return frame.origin.x
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.origin.x = newValue
+            frame = tempFrame
+        }
+    }
+    // MARK: 3.2、y 的位置
+    /// y 的位置
+    var kb_y: CGFloat {
+        get {
+            return frame.origin.y
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.origin.y = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.3、height: 视图的高度
+    /// height: 视图的高度
+    var kb_height: CGFloat {
+        get {
+            return frame.size.height
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.size.height = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.4、width: 视图的宽度
+    /// width: 视图的宽度
+    var kb_width: CGFloat {
+        get {
+            return frame.size.width
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.size.width = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.5、size: 视图的zize
+    /// size: 视图的zize
+    var kb_size: CGSize {
+        get {
+            return frame.size
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.size = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.6、centerX: 视图的X中间位置
+    /// centerX: 视图的X中间位置
+    var kb_centerX: CGFloat {
+        get {
+            return center.x
+        }
+        set(newValue) {
+            var tempCenter: CGPoint = center
+            tempCenter.x = newValue
+            center = tempCenter
+        }
+    }
+    
+    // MARK: 3.7、centerY: 视图的Y中间位置
+    /// centerY: 视图Y的中间位置
+    var kb_centerY: CGFloat {
+        get {
+            return center.y
+        }
+        set(newValue) {
+            var tempCenter: CGPoint = center
+            tempCenter.y = newValue
+            center = tempCenter
+        }
+    }
+    
+    // MARK: 3.9、top 上端横坐标(y)
+    /// top 上端横坐标(y)
+    var kb_top: CGFloat {
+        get {
+            return frame.origin.y
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.origin.y = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.10、left 左端横坐标(x)
+    /// left 左端横坐标(x)
+    var kb_left: CGFloat {
+        get {
+            return frame.origin.x
+        }
+        set(newValue) {
+            var tempFrame: CGRect = frame
+            tempFrame.origin.x = newValue
+            frame = tempFrame
+        }
+    }
+    
+    // MARK: 3.11、bottom 底端纵坐标 (y + height)
+    /// bottom 底端纵坐标 (y + height)
+    var kb_bottom: CGFloat {
+        get {
+            return frame.origin.y + frame.size.height
+        }
+        set(newValue) {
+            frame.origin.y = newValue - frame.size.height
+        }
+    }
+    
+    // MARK: 3.12、right 底端纵坐标 (x + width)
+    /// right 底端纵坐标 (x + width)
+    var kb_right: CGFloat {
+        get {
+            return frame.origin.x + frame.size.width
+        }
+        set(newValue) {
+            frame.origin.x = newValue - frame.size.width
+        }
+    }
+    
+    // MARK: 3.13、origin 点
+    /// origin 点
+    var kb_origin: CGPoint {
+        get {
+            return frame.origin
+        }
+        set(newValue) {
+            var tempOrigin: CGPoint = frame.origin
+            tempOrigin = newValue
+            frame.origin = tempOrigin
+        }
+    }
+}
+
+extension UIView {
+    
+    //添加4个不同大小的圆角
+    func addFourCorner(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat){
+        let cornerRadii = UIView.CornerRadii.init(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
+        let path = createPathWithRoundedRect(bounds: self.bounds, cornerRadii:cornerRadii)
+        let shapLayer = CAShapeLayer()
+        shapLayer.frame = self.bounds
+        shapLayer.path = path
+        self.layer.mask = shapLayer
+    }
+    
+    //各圆角大小
+    struct CornerRadii {
+        var topLeft :CGFloat = 0
+        var topRight :CGFloat = 0
+        var bottomLeft :CGFloat = 0
+        var bottomRight :CGFloat = 0
+    }
+    
+    //切圆角函数绘制线条
+    func createPathWithRoundedRect (bounds:CGRect,cornerRadii:CornerRadii) -> CGPath {
+        let minX = bounds.minX
+        let minY = bounds.minY
+        let maxX = bounds.maxX
+        let maxY = bounds.maxY
+        
+        //获取四个圆心
+        let topLeftCenterX = minX +  cornerRadii.topLeft
+        let topLeftCenterY = minY + cornerRadii.topLeft
+         
+        let topRightCenterX = maxX - cornerRadii.topRight
+        let topRightCenterY = minY + cornerRadii.topRight
+        
+        let bottomLeftCenterX = minX +  cornerRadii.bottomLeft
+        let bottomLeftCenterY = maxY - cornerRadii.bottomLeft
+         
+        let bottomRightCenterX = maxX -  cornerRadii.bottomRight
+        let bottomRightCenterY = maxY - cornerRadii.bottomRight
+        
+        //虽然顺时针参数是YES,在iOS中的UIView中,这里实际是逆时针
+        let path :CGMutablePath = CGMutablePath();
+         //顶 左
+        path.addArc(center: CGPoint(x: topLeftCenterX, y: topLeftCenterY), radius: cornerRadii.topLeft, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: false)
+        //顶右
+        path.addArc(center: CGPoint(x: topRightCenterX, y: topRightCenterY), radius: cornerRadii.topRight, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: false)
+        //底右
+        path.addArc(center: CGPoint(x: bottomRightCenterX, y: bottomRightCenterY), radius: cornerRadii.bottomRight, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false)
+        //底左
+        path.addArc(center: CGPoint(x: bottomLeftCenterX, y: bottomLeftCenterY), radius: cornerRadii.bottomLeft, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false)
+        path.closeSubpath();
+         return path;
+    }
+}
+
+// MARK: - Toast吐司🍞
+extension UIView {
+    
+    // MARK: 普通消息
+    func toast(text: String) {
+        
+        var style = ToastStyle()
+        style.messageFont = .systemFont(ofSize: 14)
+        style.backgroundColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.85)
+        style.cornerRadius = 8
+        style.verticalPadding = 14
+        style.horizontalPadding = 44
+        self.makeToast(text, duration: 1.5, position: .center, style: style)
+    }
+}

+ 35 - 0
ios/AiKeyboard/Info.plist

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ITSAppUsesNonExemptEncryption</key>
+	<false/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
+	<key>UIAppFonts</key>
+	<array>
+		<string>淘宝买菜体.ttf</string>
+	</array>
+	<key>NSExtension</key>
+	<dict>
+		<key>NSExtensionAttributes</key>
+		<dict>
+			<key>IsASCIICapable</key>
+			<false/>
+			<key>PrefersRightToLeft</key>
+			<false/>
+			<key>PrimaryLanguage</key>
+			<string>en-US</string>
+			<key>RequestsOpenAccess</key>
+			<true/>
+		</dict>
+		<key>NSExtensionPointIdentifier</key>
+		<string>com.apple.keyboard-service</string>
+		<key>NSExtensionPrincipalClass</key>
+		<string>$(PRODUCT_MODULE_NAME).KeyboardViewController</string>
+	</dict>
+</dict>
+</plist>

+ 883 - 0
ios/AiKeyboard/KeyboardViewController.swift

@@ -0,0 +1,883 @@
+//
+//  KeyboardViewController.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import UIKit
+import Lottie
+import SnapKit
+import MarqueeLabel
+
+enum KeyboardType: Int {
+    case help     = 0  // 帮聊
+    case teach    = 1  // 教你说
+    case prologue = 2  // 开场白
+}
+
+class KeyboardViewController: UIInputViewController {
+    
+    struct UX {
+        static let keyboardHeight = 292.0
+    }
+    
+    // 选择的键盘类型
+    var chooseType: KeyboardType = .help {
+        didSet {
+            self.updateMainViewUI()
+        }
+    }
+    // 键盘列表
+    var keyboardList: [KeyboardModel]?
+    // 选择的键盘
+    var chooseKeyboard: KeyboardModel?
+    // 人设列表
+    var characterList: [CharacterModel]?
+    // 开场白列表
+    var prologueList: [PrologueModel]?
+    
+    // 开始持续删除
+    private var deleteTimer: Timer?
+    private var deleteAcceleration: TimeInterval = 0.1
+    
+    var heightConstriaint: NSLayoutConstraint?
+    
+    lazy var bgImageView: UIImageView = {
+       
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "keyboard_bg")
+        return imageView
+    }()
+    
+    lazy var menuBtn: UIButton = {
+       
+        let button = UIButton()
+        button.setImage(UIImage(named: "keyboard_menu_btn"), for: .normal)
+        button.addTarget(self, action: #selector(menuBtnClickAction), for: .touchUpInside)
+        return button
+    }()
+    
+    lazy var heartAnimation: LottieAnimationView = {
+       
+        let animate = LottieAnimationView(name: "heart")
+        animate.loopMode = .loop
+        animate.backgroundColor = .clear
+        return animate
+    }()
+    
+    lazy var exchangeBtn: UIButton = {
+        
+        let button = UIButton()
+        button.setImage(UIImage(named: "keyboard_exchange_btn"), for: .normal)
+        return button
+    }()
+    
+    lazy var chooseView: UIView = {
+       
+        let view = UIView()
+        view.backgroundColor = .white
+        view.layer.cornerRadius = 17
+        return view
+    }()
+    
+    lazy var functionView: UIView = {
+        
+        let view = UIView()
+        view.backgroundColor = .hexStringColor(hexString: "#DDCFFD")
+        view.layer.cornerRadius = 16
+        
+        view.isUserInteractionEnabled = true
+        let tap = UITapGestureRecognizer(target: self, action: #selector(functionBtnClickAction))
+        view.addGestureRecognizer(tap)
+        return view
+    }()
+    
+    lazy var functionLabel: UILabel = {
+       
+        let label = UILabel()
+        label.text = "帮聊"
+        label.font = .boldSystemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        return label
+    }()
+    
+    lazy var arrowIcon: UIImageView = {
+       
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "icon_arrow_down")
+        return imageView
+    }()
+    
+    lazy var userChangeBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setTitle("小蕾", for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.8), for: .normal)
+        btn.setImage(UIImage(named: "keyboard_user_change_icon"), for: .normal)
+        btn.setImageTitleLayout(.imgRight, spacing: 8)
+        btn.addTarget(self, action: #selector(changeBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var userChangeView: UIView = {
+        
+        let view = UIView()
+        let tap = UITapGestureRecognizer(target: self, action: #selector(changeBtnClickAction))
+        view.addGestureRecognizer(tap)
+        return view
+    }()
+    
+    lazy var userChangeLabel: MarqueeLabel = {
+       
+        let label = MarqueeLabel(frame: .zero, duration: 3.0, fadeLength: 0)
+        label.type = .leftRight
+        label.text = "通用键盘"
+        label.font = .systemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000")
+        label.textAlignment = .center
+        return label
+    }()
+    
+    lazy var userChangeIcon: UIImageView = {
+       
+        let icon = UIImageView()
+        icon.image = UIImage(named: "keyboard_user_change_icon")
+        return icon
+    }()
+    
+    lazy var helpView: KeyboardHelpView = {
+       
+        let view = KeyboardHelpView()
+        view.delegate = self
+        view.helpDelegate = self
+        return view
+    }()
+    
+    lazy var teachView: KeyboardTeachView = {
+        
+        let view = KeyboardTeachView()
+        view.delegate = self
+        view.teachDelegate = self
+        view.isHidden = true
+        return view
+    }()
+    
+    lazy var prologueView: KeyboardPrologueView = {
+       
+        let view = KeyboardPrologueView()
+        view.delegate = self
+        view.prologueDelegate = self
+        view.isHidden = true
+        return view
+    }()
+    
+    lazy var exchangeView: KeyboardExchangeView = {
+        
+        let view = KeyboardExchangeView()
+        view.isHidden = true
+        view.delegate = self
+        return view
+    }()
+    
+    lazy var menuView: KeyboardMenuView = {
+        
+        let view = KeyboardMenuView()
+        view.delegate = self
+        return view
+    }()
+    
+    var nowShowView: UIView?
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        self.prepareHeightConstraint()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        setUI()
+        
+        KeyboardApi.initParams()
+        requestKeyboardList()
+        requestPrologueList()
+        startMonitoringPasteboard()
+//        self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
+//        self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
+    }
+    
+    override func viewWillLayoutSubviews() {
+        super.viewWillLayoutSubviews()
+    }
+    
+    override func updateViewConstraints() {
+        super.updateViewConstraints()
+        
+        if self.view.frame.size.width == 0 && self.view.frame.size.height == 0 {
+            return
+        }
+        
+        self.prepareHeightConstraint()
+    }
+    
+    override func textWillChange(_ textInput: UITextInput?) {
+        // The app is about to change the document's contents. Perform any preparation here.
+    }
+    
+    override func textDidChange(_ textInput: UITextInput?) {
+        // The app has just changed the document's contents, the document context has been updated.
+        
+        var textColor: UIColor
+        let proxy = self.textDocumentProxy
+        if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
+            textColor = UIColor.white
+        } else {
+            textColor = UIColor.black
+        }
+    }
+
+    func prepareHeightConstraint() {
+        
+        if self.heightConstriaint == nil {
+        
+            if let view = self.view {
+                self.heightConstriaint = NSLayoutConstraint(item: view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 0.0, constant: UX.keyboardHeight)
+                self.heightConstriaint?.priority = UILayoutPriority(750)
+                self.view.addConstraint(self.heightConstriaint!)
+            }
+        } else {
+            self.heightConstriaint?.constant = UX.keyboardHeight
+        }
+    }
+}
+
+// MARK: - 网络请求
+extension KeyboardViewController {
+    
+    // 获取键盘列表
+    func requestKeyboardList() {
+        
+        KeyboardNetworkManager.request(.keyboard_list(params: [:])) { response in
+            
+            if let keyboardInfos = try? response.mapArray(KeyboardModel.self, atKeyPath: "data.keyboardInfos") {
+                self.keyboardList = keyboardInfos
+                self.updateUserChangeBtnUI()
+                self.requestCharacterList()
+                print("成功解析数组,数量:\(keyboardInfos.count)")
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+        }
+    }
+    
+    // 获取键盘人设列表
+    func requestCharacterList() {
+        
+        var params: [String: Any] = [String: Any]()
+        if let chooseKeyboard = self.chooseKeyboard {
+            params = ["keyboardId": chooseKeyboard.id ?? ""]
+        }
+        KeyboardNetworkManager.request(.character_list(params: params)) { response in
+            
+            if let characterInfos = try? response.mapArray(CharacterModel.self, atKeyPath: "data.characterInfos") {
+                self.characterList = characterInfos
+                self.updateCharacterUI()
+                print("成功解析数组,数量:\(characterInfos.count)")
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+        }
+    }
+    
+    // 获取键盘人设列表
+    func requestPrologueList() {
+        
+        KeyboardNetworkManager.request(.prologue_list(params: [:])) { response in
+            
+            if let prologueList = try? response.mapArray(PrologueModel.self, atKeyPath: "data.prologues") {
+                self.prologueList = prologueList
+                self.updatePrologueUI()
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+        }
+    }
+    
+    // 选择键盘
+    func requestChooseKeyboard(keyboardId: String, success: @escaping (() -> Void)) {
+        
+        var params: [String: Any] = [String: Any]()
+        params = ["keyboardId": keyboardId]
+        KeyboardNetworkManager.request(.keyboard_choose(params: params)) { response in
+            
+            self.view.toast(text: "保存成功")
+            success()
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+        }
+    }
+    
+    // 请求获取超会回
+    func requestSuperReply(characterId: String, content: String, complete: @escaping ((String) -> ())) {
+        
+        var params: [String: Any] = [String: Any]()
+        params = ["keyboardId": self.chooseKeyboard?.id ?? "",
+                  "characterId": characterId,
+                  "content": content]
+        KeyboardNetworkManager.request(.chat_super_reply(params: params)) { response in
+            
+            if let contentModel = try? response.mapObject(ReplyModel.self, atKeyPath: "data") {
+                complete(contentModel.content ?? "")
+            } else {
+                complete("")
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+            complete("")
+        }
+    }
+    
+    // 请求获取超会说
+    func requestSuperSpeak(characterId: String, content: String, complete: @escaping (([String]) -> ())) {
+        
+        var params: [String: Any] = [String: Any]()
+        params = ["keyboardId": self.chooseKeyboard?.id ?? "",
+                  "characterId": characterId,
+                  "content": content]
+        KeyboardNetworkManager.request(.chat_super_speak(params: params)) { response in
+            
+            if let contentModel = try? response.mapObject(SpeakModel.self, atKeyPath: "data"), let list = contentModel.list {
+                complete(list)
+            } else {
+                complete([String]())
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+            complete([String]())
+        }
+    }
+    
+    // 请求获取超会说
+    func requestSuperPrologue(name: String, complete: @escaping (([String]) -> ())) {
+        
+        var params: [String: Any] = [String: Any]()
+        params = ["name": name]
+        KeyboardNetworkManager.request(.chat_super_prologue(params: params)) { response in
+            
+            if let contentModel = try? response.mapObject(SpeakModel.self, atKeyPath: "data"), let list = contentModel.list {
+                complete(list)
+            } else {
+                complete([String]())
+            }
+        } fail: { code, error in
+            
+            self.view.toast(text: error)
+            print(error)
+            complete([String]())
+        }
+    }
+}
+
+// MARK: - 点击事件
+extension KeyboardViewController: KeyboardMenuViewDelegate, KeyboardExchangeViewDelegate {
+    
+    // 功能按钮选择点击
+    @objc func functionBtnClickAction() {
+        
+        KeyboardFunctionPopView.show(view: self.view, selectType: self.chooseType) { type in
+            self.chooseType = type
+        }
+    }
+    
+    // 菜单按钮点击
+    @objc func menuBtnClickAction() {
+        
+        clearPopView()
+        
+        self.view.addSubview(menuView)
+        menuView.snp.makeConstraints { make in
+            make.left.right.bottom.equalTo(0)
+            make.top.equalTo(menuBtn.snp.bottom)
+        }
+    }
+    
+    // 菜单按钮返回
+    func menuBackBtnClickAction() {
+        
+        clearPopView()
+        self.nowShowView?.isHidden = false
+    }
+    
+    // 选择键盘按钮点击
+    @objc func changeBtnClickAction() {
+        
+        clearPopView()
+        self.exchangeView.keyboardList = self.keyboardList
+        self.exchangeView.isHidden = false
+    }
+    
+    // 选择键盘后返回
+    func exchangeViewBackClickAction() {
+        
+        clearPopView()
+        self.nowShowView?.isHidden = false
+    }
+    
+    // 点击保存按钮
+    func exchangeViewSaveClickAction(keyboardList: [KeyboardModel], success: @escaping (() -> ())) {
+        
+        var keyboardId = ""
+        for keyboard in keyboardList {
+            if keyboard.isChoose == true {
+                keyboardId = keyboard.id ?? ""
+                break
+            }
+        }
+        
+        requestChooseKeyboard(keyboardId: keyboardId) {
+            self.keyboardList = keyboardList
+            for keyboard in keyboardList {
+                if keyboard.isChoose == true {
+                    self.chooseKeyboard = keyboard
+                    break
+                }
+            }
+            success()
+            self.userChangeLabel.text = self.chooseKeyboard?.name
+            self.clearPopView()
+            self.nowShowView?.isHidden = false
+        }
+    }
+    
+}
+
+extension KeyboardViewController: KeyboardHelpViewDelegate, KeyboardTeachViewDelegate, KeyboardPrologueViewDelegate {
+
+    // 帮聊点击cell
+    func helpCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (() -> ())) {
+        
+        self.requestSuperReply(characterId: characterId, content: content) { text in
+            self.insertText(text)
+            complete()
+        }
+    }
+    
+    // 教你说点击cell
+    func teachCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (([String]) -> ())) {
+        
+        self.requestSuperSpeak(characterId: characterId, content: content) { list in
+            complete(list)
+        }
+    }
+    
+    // 教你说点击 TableViewCell
+    func teachTableViewDidSelectItem(content: String) {
+        
+        self.insertText(content)
+    }
+    
+    // 开场白点击cell
+    func prologueCollectionViewDidSelectItem(name: String, complete: @escaping (([String]) -> ())) {
+        
+        self.requestSuperPrologue(name: name) { list in
+            complete(list)
+        }
+    }
+    
+    // 开场白点击 TableViewCell
+    func prologueTableViewDidSelectItem(content: String) {
+        
+        self.insertText(content)
+    }
+
+}
+
+// MARK: - 处理键盘操作
+extension KeyboardViewController: KeyboardBaseViewDelegate {
+    
+    // 插入文字
+    func insertText(_ text: String) {
+        
+        self.textDocumentProxy.insertText(text)
+    }
+
+    // 点击粘贴按钮
+    func pasteBtnClickAction() {
+        
+        detectPasteboardPermissionAlert { isShowing in
+            if isShowing {
+                print("系统正在显示剪贴板权限弹窗")
+                self.view.toast(text: "请允许访问剪贴板")
+            }
+            if let content = self.getPasteboardContent() {
+                print("获取到剪贴板内容: \(content)")
+                self.view.toast(text: "获取到剪贴板内容")
+                self.helpView.setPasteStr(content: content)
+                self.teachView.setPasteStr(content: content)
+            } else {
+                print("无法获取剪贴板内容")
+                self.view.toast(text: "无法获取剪贴板内容")
+            }
+        }
+    }
+    
+    // 删除按钮
+    func deleteBtnClickAction() {
+        
+        self.textDocumentProxy.deleteBackward()
+    }
+    
+    // 清除按钮
+    func clearBtnClickAction() {
+        
+        // 获取当前文本
+        let proxy = self.textDocumentProxy
+        
+        // 清空所有文本
+        for _ in 0..<100 {
+            proxy.deleteBackward()
+        }
+    }
+    
+    // 发送按钮
+    func sendBtnClickAction() {
+        
+        self.textDocumentProxy.insertText("\n")
+    }
+    
+    func deleteBtnLongPressBegin() {
+        
+        // 停止已有的定时器
+        stopContinuousDelete()
+        
+        // 创建新的定时器,初始间隔为0.5秒
+        deleteTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(continuousDeleteAction), userInfo: nil, repeats: false)
+    }
+    
+    func deleteBtnLongPressEnd() {
+        stopContinuousDelete()
+    }
+    
+    // 停止删除
+    func stopContinuousDelete() {
+        deleteTimer?.invalidate()
+        deleteTimer = nil
+        deleteAcceleration = 0.1 // 重置加速度
+    }
+    
+    @objc func continuousDeleteAction() {
+        // 执行删除操作
+        self.textDocumentProxy.deleteBackward()
+        
+        // 停止当前定时器
+        deleteTimer?.invalidate()
+        
+        // 加速删除(最快0.05秒一次)
+        let newInterval = max(0.05, 0.5 - deleteAcceleration)
+        deleteAcceleration += 0.05 // 每次加速0.05秒
+        
+        // 创建新的更快的定时器
+        deleteTimer = Timer.scheduledTimer(timeInterval: newInterval, target: self, selector: #selector(continuousDeleteAction), userInfo: nil, repeats: false)
+    }
+    
+    // 监听剪贴板变化
+    func startMonitoringPasteboard() {
+        
+        // 记录当前剪贴板变化计数
+       var initialChangeCount = UIPasteboard.general.changeCount
+       // 记录上一次的剪贴板内容
+       var lastPasteboardContent: String? = UIPasteboard.general.string
+        
+        // 创建定时器定期检查剪贴板变化
+        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
+            guard let self = self else {
+                timer.invalidate()
+                return
+            }
+            
+            let currentChangeCount = UIPasteboard.general.changeCount
+            if currentChangeCount != initialChangeCount {
+                // 剪贴板计数已变化,检查内容是否真的变化
+                if let copiedString = UIPasteboard.general.string {
+                    // 只有当内容真的不同时才处理
+                    if !copiedString.isEmpty {
+                        print("检测到新复制的内容: \(copiedString)")
+                        // 更新记录的内容和计数
+                        lastPasteboardContent = copiedString
+                        initialChangeCount = currentChangeCount
+                        // 处理复制的内容
+                        self.handleCopiedContent(copiedString)
+                    } else {
+                        // 内容相同或为空,只更新计数
+                        initialChangeCount = currentChangeCount
+                        print("剪贴板计数变化但内容未变或为空")
+                    }
+                } else {
+                    // 剪贴板内容为nil,更新计数
+                    initialChangeCount = currentChangeCount
+                    lastPasteboardContent = nil
+                    print("剪贴板内容被清空")
+                }
+            }
+        }
+    }
+    
+    // 处理复制的内容
+    func handleCopiedContent(_ content: String) {
+        
+        self.helpView.setPasteStr(content: content)
+        self.teachView.setPasteStr(content: content)
+    }
+    
+    // 检查键盘权限
+    func checkPasteboardPermission() -> Bool {
+         
+        var hasPasteboardPermission = false
+        // iOS 14及以上版本需要检查权限
+        if #available(iOS 14.0, *) {
+            // 尝试读取剪贴板内容来检查权限
+            let pasteboard = UIPasteboard.general
+            
+            if let _ = pasteboard.string {
+                hasPasteboardPermission = true
+                print("键盘扩展有剪贴板访问权限")
+            } else {
+                hasPasteboardPermission = false
+                print("键盘扩展没有剪贴板访问权限")
+            }
+        } else {
+            // iOS 14以下版本默认有权限
+            hasPasteboardPermission = true
+        }
+
+        return hasPasteboardPermission
+    }
+    
+    // 检测系统是否弹出剪贴板权限弹窗
+    func detectPasteboardPermissionAlert(completion: @escaping (Bool) -> Void) {
+        // 记录访问前的时间
+        let startTime = Date()
+        
+        // 尝试访问剪贴板
+        let _ = UIPasteboard.general.string
+        
+        // 检查访问后的时间延迟
+        let timeDelay = Date().timeIntervalSince(startTime)
+        
+        // 如果访问剪贴板的时间超过一定阈值,可能是因为系统弹出了权限弹窗
+        // 通常正常访问剪贴板的时间应该很短,如果超过100毫秒,可能是弹出了弹窗
+        if timeDelay > 0.1 {
+            completion(true)
+        } else {
+            completion(false)
+        }
+    }
+    
+    // 获取剪贴板内容
+    func getPasteboardContent() -> String? {
+        let pasteboard = UIPasteboard.general
+        return pasteboard.string
+    }
+    
+}
+
+extension KeyboardViewController {
+    
+    // 关闭其他弹出页
+    func clearPopView() {
+        
+        self.menuView.removeFromSuperview()
+        self.exchangeView.isHidden = true
+        self.helpView.isHidden = true
+        self.teachView.isHidden = true
+        self.prologueView.isHidden = true
+    }
+    
+    // 更新主要界面
+    func updateMainViewUI() {
+        
+        clearPopView()
+        
+        switch chooseType {
+        case .help:
+//            self.helpView.characterList = self.characterList
+            self.helpView.isHidden = false
+            self.nowShowView = self.helpView
+            self.functionLabel.text = "帮聊"
+            break
+        case .teach:
+//            self.teachView.characterList = self.characterList
+            self.teachView.isHidden = false
+            self.nowShowView = self.teachView
+            self.functionLabel.text = "教你说"
+            break
+        case .prologue:
+            self.prologueView.isHidden = false
+            self.nowShowView = self.prologueView
+            self.functionLabel.text = "开场白"
+            break
+        }
+    }
+    
+    // 更新人设列表
+    func updateCharacterUI() {
+        
+        self.helpView.characterList = self.characterList
+        self.teachView.characterList = self.characterList
+    }
+    
+    // 更新开场白
+    func updatePrologueUI() {
+        
+        self.prologueView.prologueList = self.prologueList
+    }
+    
+    // 更新键盘选择按钮
+    func updateUserChangeBtnUI() {
+        
+        if self.keyboardList?.count == 1 {
+            
+            self.keyboardList?[0].isChoose = true
+            self.chooseKeyboard = self.keyboardList?.first
+        } else {
+            
+            if let keyboardList = self.keyboardList {
+                for keyboard in keyboardList {
+                    if keyboard.isChoose == true {
+                        self.chooseKeyboard = keyboard
+                    }
+                }
+            }
+        }
+        
+        if let chooseKeyboard = self.chooseKeyboard {
+            self.userChangeLabel.text = chooseKeyboard.name
+        }
+    }
+    
+    func setUI() {
+        
+        self.view.addSubview(bgImageView)
+        bgImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        self.view.addSubview(menuBtn)
+        menuBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(13)
+            make.top.equalTo(17)
+        }
+        
+        self.view.addSubview(heartAnimation)
+        heartAnimation.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 35, height: 30))
+            make.right.equalTo(-10)
+            make.centerY.equalTo(menuBtn.snp.centerY)
+        }
+        heartAnimation.play()
+        
+        self.view.addSubview(exchangeBtn)
+        exchangeBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 34, height: 34))
+            make.right.equalTo(heartAnimation.snp.left).offset(-10)
+            make.centerY.equalTo(menuBtn.snp.centerY)
+        }
+        
+        self.view.addSubview(teachView)
+        teachView.snp.makeConstraints { make in
+            make.left.right.bottom.equalTo(0)
+            make.top.equalTo(60)
+        }
+        
+        self.view.addSubview(helpView)
+        helpView.snp.makeConstraints { make in
+            make.left.right.bottom.equalTo(0)
+            make.top.equalTo(60)
+        }
+        
+        self.view.addSubview(prologueView)
+        prologueView.snp.makeConstraints { make in
+            make.left.right.bottom.equalTo(0)
+            make.top.equalTo(menuBtn.snp.bottom)
+        }
+        
+        self.view.addSubview(chooseView)
+        chooseView.snp.makeConstraints { make in
+            make.width.equalTo(147)
+            make.height.equalTo(34)
+            make.left.equalTo(menuBtn.snp.right).offset(9)
+            make.centerY.equalTo(menuBtn.snp.centerY)
+        }
+        
+        self.view.addSubview(exchangeView)
+        exchangeView.snp.makeConstraints { make in
+            make.top.equalTo(menuBtn.snp.bottom)
+            make.left.right.bottom.equalTo(0)
+        }
+        
+        chooseView.addSubview(functionView)
+        functionView.snp.makeConstraints { make in
+            make.top.left.equalTo(1)
+            make.bottom.equalTo(-1)
+            make.width.equalTo(85)
+        }
+        
+        functionView.addSubview(functionLabel)
+        functionLabel.snp.makeConstraints { make in
+            make.left.equalTo(12)
+            make.centerY.equalToSuperview()
+        }
+        
+        functionView.addSubview(arrowIcon)
+        arrowIcon.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 18, height: 18))
+            make.centerY.equalToSuperview()
+            make.right.equalTo(-5)
+        }
+        
+        chooseView.addSubview(userChangeView)
+        userChangeView.snp.makeConstraints { make in
+            make.left.equalTo(functionView.snp.right)
+            make.right.equalToSuperview()
+            make.top.bottom.equalTo(0)
+        }
+        
+        userChangeView.addSubview(userChangeIcon)
+        userChangeIcon.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 12, height: 12))
+            make.centerY.equalToSuperview()
+            make.right.equalTo(-8)
+        }
+        
+        userChangeView.addSubview(userChangeLabel)
+        userChangeLabel.snp.makeConstraints { make in
+            make.right.equalTo(userChangeIcon.snp.left).offset(-4)
+            make.left.equalTo(4)
+            make.centerY.equalToSuperview()
+        }
+        
+//        chooseView.addSubview(userChangeBtn)
+//        userChangeBtn.snp.makeConstraints { make in
+//            make.left.equalTo(functionView.snp.right)
+//            make.right.equalToSuperview()
+//            make.top.bottom.equalTo(0)
+//        }
+        
+        self.nowShowView = self.helpView
+    }
+}

+ 21 - 0
ios/AiKeyboard/Marco/KeyboardConst.swift

@@ -0,0 +1,21 @@
+//
+//  KeyboardConst.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import Foundation
+import UIKit
+
+struct KeyboardConst {
+    
+    // MARK: 2.1、屏幕的宽
+    /// 屏幕的宽
+    static var kb_kScreenW: CGFloat { return UIScreen.main.bounds.width }
+    
+    // MARK: 2.2、屏幕的高
+    /// 屏幕的高
+    static var kb_kScreenH: CGFloat { return UIScreen.main.bounds.height }
+    
+}

+ 6 - 0
ios/AiKeyboard/Media.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 22 - 0
ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_arrow_down@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "icon_arrow_down@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/icon_arrow_down@2x.png


BIN
ios/AiKeyboard/Media.xcassets/icon_arrow_down.imageset/icon_arrow_down@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_arrow_up@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "icon_arrow_up@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/icon_arrow_up@2x.png


BIN
ios/AiKeyboard/Media.xcassets/icon_arrow_up.imageset/icon_arrow_up@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/icon_plus.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "icon_plus@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "icon_plus@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/icon_plus.imageset/icon_plus@2x.png


BIN
ios/AiKeyboard/Media.xcassets/icon_plus.imageset/icon_plus@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_back_btn@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_back_btn@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/keyboard_back_btn@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_back_btn.imageset/keyboard_back_btn@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_bg@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_bg@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/keyboard_bg@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_bg.imageset/keyboard_bg@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_copy_icon@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_copy_icon@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/keyboard_copy_icon@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_copy_icon.imageset/keyboard_copy_icon@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_exchange_btn@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_exchange_btn@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/keyboard_exchange_btn@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_exchange_btn.imageset/keyboard_exchange_btn@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_exchange_normal@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_exchange_normal@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/keyboard_exchange_normal@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_exchange_normal.imageset/keyboard_exchange_normal@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_func_select_bg@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_func_select_bg@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/keyboard_func_select_bg@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_func_select_bg.imageset/keyboard_func_select_bg@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_heart_btn@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_heart_btn@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/keyboard_heart_btn@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_heart_btn.imageset/keyboard_heart_btn@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_logo@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_logo@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/keyboard_logo@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_logo.imageset/keyboard_logo@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_menu_button@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_menu_button@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/keyboard_menu_button@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_btn.imageset/keyboard_menu_button@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_menu_diy@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_menu_diy@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/keyboard_menu_diy@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_diy.imageset/keyboard_menu_diy@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_menu_market@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_menu_market@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/keyboard_menu_market@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_market.imageset/keyboard_menu_market@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_menu_vip@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_menu_vip@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/keyboard_menu_vip@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_menu_vip.imageset/keyboard_menu_vip@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_normal_btn_bg@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_normal_btn_bg@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/keyboard_normal_btn_bg@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_normal_btn_bg.imageset/keyboard_normal_btn_bg@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_paste_btn_bg@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_paste_btn_bg@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/keyboard_paste_btn_bg@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_paste_btn_bg.imageset/keyboard_paste_btn_bg@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_text_clear_btn@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_text_clear_btn@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/keyboard_text_clear_btn@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_text_clear_btn.imageset/keyboard_text_clear_btn@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_user_change_icon@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_user_change_icon@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/keyboard_user_change_icon@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_user_change_icon.imageset/keyboard_user_change_icon@3x.png


+ 22 - 0
ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "keyboard_vip_top@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "keyboard_vip_top@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/keyboard_vip_top@2x.png


BIN
ios/AiKeyboard/Media.xcassets/keyboard_vip_top.imageset/keyboard_vip_top@3x.png


+ 49 - 0
ios/AiKeyboard/Model/CharacterModel.swift

@@ -0,0 +1,49 @@
+//
+//  CharacterModel.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/29.
+//
+
+import Foundation
+import ObjectMapper
+
+// MARK: Initializer and Properties
+struct CharacterModel: Mappable {
+
+    var id: String?
+    
+    var name: String?
+    
+    var imageUrl: Int?
+    
+    var description: String?
+    
+    var emoji: String?
+    
+    var isVip: Bool?
+    
+    var birthday: String?
+    
+    var hobbies: [String]?
+    
+    var characters: [String]?
+    
+    var gender: Int?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        id <- map["id"]
+        name <- map["name"]
+        imageUrl <- map["imageUrl"]
+        description <- map["description"]
+        emoji <- map["emoji"]
+        isVip <- map["isVip"]
+        birthday <- map["birthday"]
+        hobbies <- map["hobbies"]
+        characters <- map["characters"]
+        gender <- map["gender"]
+    }
+}

+ 34 - 0
ios/AiKeyboard/Model/ChatModel.swift

@@ -0,0 +1,34 @@
+//
+//  ChatModel.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/30.
+//
+
+import Foundation
+import ObjectMapper
+
+// MARK: Initializer and Properties
+struct ReplyModel: Mappable {
+    
+    var content: String?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        content <- map["content"]
+    }
+}
+
+struct SpeakModel: Mappable {
+    
+    var list: [String]?
+    
+    // MARK: JSON
+    init?(map: Map) { }
+    
+    mutating func mapping(map: Map) {
+        list <- map["list"]
+    }
+}

+ 47 - 0
ios/AiKeyboard/Model/KeyboardModel.swift

@@ -0,0 +1,47 @@
+//
+//  KeyboardModel.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/29.
+//
+
+import Foundation
+import ObjectMapper
+
+// MARK: Initializer and Properties
+struct KeyboardModel: Mappable {
+
+    var id: String?
+    /*
+    类型
+    system:系统键盘
+    custom:定制键盘
+    */
+    var type: String?
+    
+    var name: String?
+    
+    var gender: Int?
+    
+    var birthday: String?
+    
+    var intimacy: Int?
+    
+    var imageUrl: String?
+    
+    var isChoose: Bool?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        id <- map["id"]
+        type <- map["type"]
+        name <- map["name"]
+        gender <- map["gender"]
+        birthday <- map["birthday"]
+        intimacy <- map["intimacy"]
+        imageUrl <- map["imageUrl"]
+        isChoose <- map["isChoose"]
+    }
+}

+ 36 - 0
ios/AiKeyboard/Model/PrologueModel.swift

@@ -0,0 +1,36 @@
+//
+//  PrologueModel.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/30.
+//
+
+import Foundation
+import ObjectMapper
+
+struct PrologueModel: Mappable {
+    
+    var title: String?
+    
+    var topics: [PrologueTopicModel]?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        title <- map["title"]
+        topics <- map["topics"]
+    }
+}
+
+struct PrologueTopicModel: Mappable {
+    
+    var name: String?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        name <- map["name"]
+    }
+}

+ 76 - 0
ios/AiKeyboard/Model/UserModel.swift

@@ -0,0 +1,76 @@
+//
+//  UserModel.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/29.
+//
+
+import Foundation
+import ObjectMapper
+
+// MARK: Initializer and Properties
+struct UserModel: Mappable {
+
+    var userId: String?
+
+    var ssid: String?
+    
+    var deviceId: String?
+    
+    var phone: String?
+    
+    var loginStatus: Int?
+    
+    var channelName: String?
+    
+    var name: String?
+    
+    var gender: Int?
+    
+    var birthday: String?
+    
+    var userIdOrSsid: String?
+    
+    var imageUrl: String?
+    
+    var account: String?
+    
+    var memberInfo: MemberModel?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        userId <- map["userId"]
+        ssid <- map["ssid"]
+        deviceId <- map["deviceId"]
+        phone <- map["phone"]
+        loginStatus <- map["loginStatus"]
+        channelName <- map["channelName"]
+        name <- map["name"]
+        gender <- map["gender"]
+        birthday <- map["birthday"]
+        userIdOrSsid <- map["userIdOrSsid"]
+        imageUrl <- map["imageUrl"]
+        account <- map["account"]
+        memberInfo <- map["memberInfo"]
+    }
+}
+
+struct MemberModel: Mappable {
+    
+    var isMember: Bool?
+
+    var endTimestamp: Int?
+    
+    var permanent: Bool?
+
+    // MARK: JSON
+    init?(map: Map) { }
+    
+    mutating func mapping(map: Map) {
+        isMember <- map["isMember"]
+        endTimestamp <- map["endTimestamp"]
+        permanent <- map["permanent"]
+    }
+}

File diff suppressed because it is too large
+ 167 - 0
ios/AiKeyboard/Network/KeyboardApi.swift


+ 24 - 0
ios/AiKeyboard/Network/KeyboardBaseResponse.swift

@@ -0,0 +1,24 @@
+//
+//  KeyboardBaseResponse.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import Foundation
+import ObjectMapper
+
+// MARK: Initializer and Properties
+struct KeyboardBaseResponse: Mappable {
+
+    var code: Int!
+    var msg: String?
+
+    // MARK: JSON
+    init?(map: Map) { }
+
+    mutating func mapping(map: Map) {
+        code <- map["code"]
+        msg <- map["msg"]
+    }
+}

+ 124 - 0
ios/AiKeyboard/Network/KeyboardNetworkManager.swift

@@ -0,0 +1,124 @@
+//
+//  KeyboardNetworkManager.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import Moya
+import Moya_ObjectMapper
+
+let KeyboardNetworkProvider = MoyaProvider<KeyboardClientService>(plugins: [networkLoggerPlugin])
+let networkLoggerPlugin = NetworkLoggerPlugin()
+
+enum KeyboardClientService {
+    
+    case character_list(params: [String: Any])
+    case keyboard_list(params: [String: Any])
+    case prologue_list(params: [String: Any])
+    case keyboard_choose(params: [String: Any])
+    case chat_super_reply(params: [String: Any])
+    case chat_super_speak(params: [String: Any])
+    case chat_super_prologue(params: [String: Any])
+}
+
+extension KeyboardClientService: TargetType {
+    
+    var baseURL: URL {
+        let baseUrlStr = KeyboardApi.getBaseUrl()
+        return URL(string: baseUrlStr)!
+    }
+    
+    var path: String {
+        switch self {
+        case .character_list: return KeyboardApi.character_list
+        case .keyboard_list: return KeyboardApi.keyboard_list
+        case .prologue_list: return KeyboardApi.prologue_list
+        case .keyboard_choose: return KeyboardApi.keyboard_choose
+        case .chat_super_reply: return KeyboardApi.chat_super_reply
+        case .chat_super_speak: return KeyboardApi.chat_super_speak
+        case .chat_super_prologue: return KeyboardApi.chat_super_prologue
+        }
+    }
+    
+    var method: Moya.Method {
+        return .post
+    }
+    
+    var sampleData: Data {
+        return "".data(using: String.Encoding.utf8)!
+    }
+    
+    var task: Moya.Task {
+        var parameters: [String: Any] = KeyboardApi.params
+        switch self {
+        case .character_list(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .keyboard_list(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .prologue_list(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .keyboard_choose(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .chat_super_reply(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .chat_super_speak(let params):
+            parameters.merge(params) { (current, _) in current }
+        case .chat_super_prologue(let params):
+            parameters.merge(params) { (current, _) in current }
+        }
+        debugPrint(parameters)
+        return Task.requestParameters(parameters:parameters, encoding: JSONEncoding.default)
+    }
+    
+    var headers: [String : String]? {
+        return ["Content-type": "application/json"]
+    }
+}
+
+struct KeyboardNetworkManager {
+    
+    typealias completeCallback = ((Response) -> (Void))
+    typealias failCallback = ((Int, String) -> (Void))
+    
+    static func request(_ target: KeyboardClientService, success: @escaping completeCallback, fail: @escaping failCallback){
+        
+        
+        KeyboardNetworkProvider.request(target) { result in
+            //隐藏hud
+            switch result {
+            case let .success(response):
+                
+                if response.statusCode == 200 {
+
+                    if let responseData = try? response.mapObject(KeyboardBaseResponse.self){
+                        if responseData.code == 0 {
+                            success(response)
+                        } else {
+                            fail(responseData.code, responseData.msg ?? "")
+                        }
+                    } else {
+                        fail(500, "解析失败")
+                    }
+                
+                } else {
+                    
+//                    fail(response.statusCode, response.toJSON(modelKey: "msg").stringValue)
+                }
+                 
+                #if DEBUG
+                if let json = try? response.mapJSON() {
+                    print("data:\(json)")
+                }
+                #endif
+                    
+                break
+            case let .failure(error):
+                fail(error.errorCode, "网络错误,请检查后重试")
+                //网络连接失败,提示用户
+//                UIApplication.keyWindow?.toast(text: "网络错误,请检查后重试")
+                break
+            }
+        }
+    }
+}

+ 115 - 0
ios/AiKeyboard/View/Cell/KeyboardCharacterCell.swift

@@ -0,0 +1,115 @@
+//
+//  KeyboardCharacterCell.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import UIKit
+import Lottie
+import MarqueeLabel
+
+class KeyboardCharacterCell: UICollectionViewCell {
+    
+    static func reuseIdentifier() -> String {
+        return "KeyboardCharacterCell"
+    }
+    
+    var characterModel: CharacterModel?
+    
+    lazy var containerView: UIView = {
+        
+        let view = UIView()
+        view.layer.cornerRadius = 9
+        view.backgroundColor = .white
+        return view
+    }()
+    
+    lazy var titleLabel: MarqueeLabel = {
+       
+        let label = MarqueeLabel(frame: .zero, duration: 3.0, fadeLength: 0)
+        label.type = .leftRight
+        label.textAlignment = .center
+        label.text = "💑 恋爱脑"
+        label.font = .systemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000").withAlphaComponent(0.8)
+        return label
+    }()
+    
+    lazy var addBtn: UIButton = {
+       
+        let btn = UIButton(frame: CGRect(x: 0, y: 0, width: KeyboardHelpView.UX.cellWidth, height: KeyboardHelpView.UX.cellHeight))
+        btn.isHidden = true
+        btn.setTitle("添加人设", for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 12)
+        btn.setTitleColor(.hexStringColor(hexString: "#7D46FC"), for: .normal)
+        btn.setImage(UIImage(named: "icon_plus"), for: .normal)
+        btn.setImageTitleLayout(.imgLeft, spacing: 3)
+        return btn
+    }()
+    
+    lazy var loadingAnimate: LottieAnimationView = {
+       
+        let animate = LottieAnimationView(name: "keyboard_loading")
+        animate.isHidden = true
+        animate.loopMode = .loop
+        animate.backgroundColor = .clear
+        return animate
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        self.contentView.addSubview(containerView)
+        containerView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        self.containerView.addSubview(titleLabel)
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalTo(4)
+            make.right.equalTo(-4)
+        }
+        
+        self.containerView.addSubview(addBtn)
+        addBtn.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        self.containerView.addSubview(loadingAnimate)
+        loadingAnimate.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.center.equalToSuperview()
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func config(model: CharacterModel) {
+        self.characterModel = model
+        if let emoji = model.emoji, let name = model.name {
+            self.titleLabel.text = "\(emoji) \(name)"
+        }
+    }
+    
+    func config(content: String) {
+        self.titleLabel.text = "\(content)"
+    }
+    
+    // 设置动画
+    func setAnimate(play: Bool) {
+        
+        if play {
+            self.titleLabel.isHidden = true
+            self.loadingAnimate.isHidden = false
+            self.loadingAnimate.play()
+        } else {
+            self.titleLabel.isHidden = false
+            self.loadingAnimate.isHidden = true
+            self.loadingAnimate.pause()
+        }
+    }
+}

+ 103 - 0
ios/AiKeyboard/View/Cell/KeyboardExchangeCell.swift

@@ -0,0 +1,103 @@
+//
+//  KeyboardExchangeCell.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import UIKit
+import Kingfisher
+
+class KeyboardExchangeCell: UICollectionViewCell {
+    
+    static func reuseIdentifier() -> String {
+        return "KeyboardExchangeCell"
+    }
+    
+    var keyboardModel: KeyboardModel?
+    
+    lazy var containerView: UIView = {
+       
+        let view = UIView()
+        view.layer.cornerRadius = 14
+        view.backgroundColor = .white
+        return view
+    }()
+    
+    lazy var iconImageView: UIImageView = {
+        
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "keyboard_exchange_normal")
+        imageView.clipsToBounds = true
+        imageView.layer.cornerRadius = 8
+        return imageView
+    }()
+    
+    lazy var keyboardLabel: UILabel = {
+       
+        let label = UILabel()
+        label.text = "通用键盘"
+        label.font = .systemFont(ofSize: 12, weight: .medium)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        return label
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func config(model: KeyboardModel) {
+        
+        self.keyboardModel = model
+        self.keyboardLabel.text = model.name
+        if model.type == "custom" {
+            if let url = URL(string: model.imageUrl ?? "") {
+                self.iconImageView.kf.setImage(with: url, placeholder: UIImage(named: "keyboard_exchange_normal"))
+            }
+        } else if model.type == "system" {
+            self.iconImageView.image = UIImage(named: "keyboard_exchange_normal")
+        }
+        
+        if model.isChoose == true {
+            
+            if let image = UIImage.gradient([UIColor.hexStringColor(hexString: "#7D46FC"), UIColor.hexStringColor(hexString: "#BC87FF")], size: CGSize(width: KeyboardExchangeView.UX.cellWidth, height: KeyboardExchangeView.UX.cellHeight), locations: [0, 1], direction: .horizontal) {
+                
+                containerView.backgroundColor = UIColor(patternImage: image)
+            }
+            self.keyboardLabel.textColor = .white
+        } else {
+            
+            containerView.backgroundColor = .white
+            self.keyboardLabel.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        }
+    }
+}
+
+extension KeyboardExchangeCell {
+    
+    func initUI() {
+        
+        self.contentView.addSubview(containerView)
+        containerView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        containerView.addSubview(iconImageView)
+        iconImageView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 36, height: 36))
+            make.top.equalTo(10)
+            make.centerX.equalToSuperview()
+        }
+        
+        containerView.addSubview(keyboardLabel)
+        keyboardLabel.snp.makeConstraints { make in
+            make.top.equalTo(iconImageView.snp.bottom).offset(8)
+            make.centerX.equalToSuperview()
+        }
+    }
+}

+ 123 - 0
ios/AiKeyboard/View/Cell/KeyboardTeachDialogueCell.swift

@@ -0,0 +1,123 @@
+//
+//  KeyboardTeachDialogueCell.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import UIKit
+import Lottie
+
+class KeyboardTeachDialogueCell: UITableViewCell {
+    
+    static func reuseIdentifier() -> String {
+        return "KeyboardTeachDialogueCell"
+    }
+    
+    lazy var containerView: UIView = {
+       
+        let view = UIView(frame: CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW - 84, height: KeyboardTeachView.UX.resultCellHeight))
+        view.backgroundColor = .white
+        view.addFourCorner(topLeft: 14, topRight: 18, bottomLeft: 0, bottomRight: 18)
+        
+        return view
+    }()
+    
+    lazy var dialogueLabel: UILabel = {
+       
+        let label = UILabel()
+        label.isHidden = true
+        label.numberOfLines = 0
+        label.text = "巴拉巴拉小魔仙?"
+        label.font = .systemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        label.setLineSpacing(5)
+        return label
+    }()
+    
+    lazy var loadingAnimate: LottieAnimationView = {
+       
+        let animate = LottieAnimationView(name: "keyboard_dialog_loading")
+        animate.loopMode = .loop
+        animate.backgroundColor = .clear
+        return animate
+    }()
+    
+    lazy var loadingLabel: UILabel = {
+
+        let label = UILabel()
+        label.text = "加载中..."
+        label.font = .systemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.47)
+        return label
+    }()
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        self.backgroundColor = .clear
+        
+        self.contentView.addSubview(containerView)
+//        containerView.snp.makeConstraints { make in
+//            make.left.right.top.equalTo(0)
+//            make.height.equalTo(height)
+//        }
+        
+        containerView.addSubview(dialogueLabel)
+        dialogueLabel.snp.makeConstraints { make in
+            make.left.equalTo(11)
+            make.right.equalTo(-10)
+            make.centerY.equalToSuperview()
+        }
+        
+        containerView.addSubview(loadingAnimate)
+        containerView.addSubview(loadingLabel)
+        
+        loadingAnimate.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 14, height: 14))
+            make.left.equalTo(10)
+            make.centerY.equalToSuperview()
+        }
+        loadingAnimate.play()
+        
+        loadingLabel.snp.makeConstraints { make in
+            make.left.equalTo(loadingAnimate.snp.right).offset(8)
+            make.centerY.equalToSuperview()
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func setLoading(play: Bool) {
+        
+        if play {
+            
+            self.dialogueLabel.isHidden = true
+            self.loadingAnimate.isHidden = false
+            self.loadingLabel.isHidden = false
+            self.loadingAnimate.play()
+            
+            self.containerView.frame = CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW - 84, height: KeyboardTeachView.UX.resultCellHeight)
+            self.containerView.addFourCorner(topLeft: 14, topRight: 18, bottomLeft: 0, bottomRight: 18)
+        } else {
+            
+            self.dialogueLabel.isHidden = false
+            self.loadingAnimate.isHidden = true
+            self.loadingLabel.isHidden = true
+            self.loadingAnimate.pause()
+        }
+    }
+    
+    func config(content: String) {
+        
+        setLoading(play: false)
+        
+        self.dialogueLabel.text = content
+        
+        let height = content.heightAccording(width: KeyboardConst.kb_kScreenW - 105, font: .systemFont(ofSize: 12), lineSpacing: 5) + 16
+        self.containerView.frame = CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW - 84, height: height)
+        self.containerView.addFourCorner(topLeft: 14, topRight: 18, bottomLeft: 0, bottomRight: 18)
+    }
+}

+ 255 - 0
ios/AiKeyboard/View/MainView/KeyboardBaseView.swift

@@ -0,0 +1,255 @@
+//
+//  KeyboardBaseView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import UIKit
+
+protocol KeyboardBaseViewDelegate: NSObjectProtocol {
+    
+    func pasteBtnClickAction()
+    
+    func deleteBtnClickAction()
+    
+    func clearBtnClickAction()
+    
+    func sendBtnClickAction()
+    
+    // 长按删除方法
+    func deleteBtnLongPressBegin()
+    func deleteBtnLongPressEnd()
+}
+
+class KeyboardBaseView: UIView {
+    
+    weak var delegate: KeyboardBaseViewDelegate?
+    
+    lazy var pasteBtn: UIButton = {
+        
+        let button = UIButton()
+        button.setBackgroundImage(UIImage(named: "keyboard_paste_btn_bg"), for: .normal)
+        button.setTitle("粘贴", for: .normal)
+        button.setTitleColor(.white, for: .normal)
+        button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
+        button.addTarget(self, action: #selector(pasteBtnAction), for: .touchUpInside)
+        return button
+    }()
+    
+    lazy var deleteBtn: UIButton = {
+        
+        let button = UIButton()
+        button.setBackgroundImage(UIImage(named: "keyboard_normal_btn_bg"), for: .normal)
+        button.setTitle("删除", for: .normal)
+        button.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.9), for: .normal)
+        button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
+        button.addTarget(self, action: #selector(deleteBtnAction), for: .touchUpInside)
+        
+        // 添加长按手势
+        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleDeleteLongPress(_:)))
+        longPressGesture.minimumPressDuration = 0.5 // 设置长按触发时间为0.5秒
+        button.addGestureRecognizer(longPressGesture)
+        return button
+    }()
+    
+    lazy var clearBtn: UIButton = {
+        
+        let button = UIButton()
+        button.setBackgroundImage(UIImage(named: "keyboard_normal_btn_bg"), for: .normal)
+        button.setTitle("清空", for: .normal)
+        button.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.9), for: .normal)
+        button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
+        button.addTarget(self, action: #selector(clearBtnAction), for: .touchUpInside)
+        return button
+    }()
+    
+    lazy var sendBtn: UIButton = {
+        
+        let button = UIButton()
+        button.setBackgroundImage(UIImage(named: "keyboard_normal_btn_bg"), for: .normal)
+        button.setTitle("发送", for: .normal)
+        button.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.9), for: .normal)
+        button.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
+        button.addTarget(self, action: #selector(sendBtnAction), for: .touchUpInside)
+        return button
+    }()
+    
+    lazy var dialogueView: UIView = {
+       
+        let view = UIView()
+        view.layer.cornerRadius = 10
+        view.backgroundColor = .white
+        return view
+    }()
+    
+    // 对话框
+    lazy var dialogueTitleLabel: UILabel = {
+       
+        let label = UILabel()
+        
+        let attr = NSMutableAttributedString()
+        let copyIcon = NSTextAttachment(image: UIImage(named: "keyboard_copy_icon")!)
+        copyIcon.bounds = CGRect(x: -4, y: -4, width: 18, height: 18)
+        let copyAttr = NSAttributedString(attachment: copyIcon)
+        attr.append(copyAttr)
+        
+        let titleText = NSMutableAttributedString(string: "复制对方的话自动粘贴")
+        titleText.addAttributes([.font: UIFont.systemFont(ofSize: 14, weight: .medium), .foregroundColor: UIColor.hexStringColor(hexString: "#996DFF")], range: NSRange(location: 0, length: titleText.length))
+        attr.append(titleText)
+
+        label.attributedText = attr
+        return label
+    }()
+    
+    lazy var dialogueLabel: UILabel = {
+      
+        let label = UILabel()
+        label.isHidden = true
+        label.textAlignment = .center
+        label.text = ""
+        label.textColor = .hexStringColor(hexString: "#996DFF")
+        label.font = .systemFont(ofSize: 14, weight: .medium)
+        return label
+    }()
+    
+    lazy var dialogueClearBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.isHidden = true
+        btn.setImage(UIImage(named: "keyboard_text_clear_btn"), for: .normal)
+        btn.addTarget(self, action: #selector(dialogueClearBtnAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardBaseView {
+    
+    func setPasteStr(content: String) {
+        
+        self.dialogueTitleLabel.isHidden = true
+        self.dialogueLabel.isHidden = false
+        self.dialogueClearBtn.isHidden = false
+        self.dialogueLabel.text = content
+    }
+}
+
+extension KeyboardBaseView {
+    
+    // 对话框清除按钮
+    @objc func dialogueClearBtnAction() {
+        
+        self.dialogueTitleLabel.isHidden = false
+        self.dialogueLabel.isHidden = true
+        self.dialogueClearBtn.isHidden = true
+        self.dialogueLabel.text = ""
+    }
+    
+    // 粘贴按钮
+    @objc func pasteBtnAction() {
+        
+        delegate?.pasteBtnClickAction()
+    }
+    
+    // 删除按钮
+    @objc func deleteBtnAction() {
+        
+        delegate?.deleteBtnClickAction()
+    }
+    
+    // 处理长按手势
+    @objc func handleDeleteLongPress(_ gesture: UILongPressGestureRecognizer) {
+        switch gesture.state {
+        case .began:
+            // 长按开始
+            delegate?.deleteBtnLongPressBegin()
+        case .ended, .cancelled:
+            // 长按结束或取消
+            delegate?.deleteBtnLongPressEnd()
+        default:
+            break
+        }
+    }
+    
+    // 清除按钮
+    @objc func clearBtnAction() {
+        
+        delegate?.clearBtnClickAction()
+    }
+    
+    // 发送按钮
+    @objc func sendBtnAction() {
+        
+        delegate?.sendBtnClickAction()
+    }
+}
+
+extension KeyboardBaseView {
+    
+    func setUI() {
+        
+        self.addSubview(pasteBtn)
+        pasteBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 58, height: 46))
+            make.right.equalTo(-10)
+            make.top.equalTo(0)
+        }
+        
+        self.addSubview(deleteBtn)
+        deleteBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 58, height: 46))
+            make.right.equalTo(-10)
+            make.top.equalTo(pasteBtn.snp.bottom).offset(6)
+        }
+        
+        self.addSubview(clearBtn)
+        clearBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 58, height: 46))
+            make.right.equalTo(-10)
+            make.top.equalTo(deleteBtn.snp.bottom).offset(6)
+        }
+        
+        self.addSubview(sendBtn)
+        sendBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 58, height: 52))
+            make.right.equalTo(-10)
+            make.top.equalTo(clearBtn.snp.bottom).offset(6)
+        }
+        
+        self.addSubview(dialogueView)
+        dialogueView.snp.makeConstraints { make in
+            make.height.equalTo(46)
+            make.top.equalTo(0)
+            make.left.equalTo(10)
+            make.right.equalTo(pasteBtn.snp.left).offset(-6)
+        }
+        
+        dialogueView.addSubview(dialogueTitleLabel)
+        dialogueTitleLabel.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
+        
+        dialogueView.addSubview(dialogueClearBtn)
+        dialogueClearBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 16, height: 16))
+            make.right.equalTo(-10)
+            make.centerY.equalToSuperview()
+        }
+        
+        dialogueView.addSubview(dialogueLabel)
+        dialogueLabel.snp.makeConstraints { make in
+            make.left.equalTo(12)
+            make.right.equalTo(dialogueClearBtn.snp.left).offset(-12)
+            make.centerY.equalToSuperview()
+        }
+    }
+}

+ 127 - 0
ios/AiKeyboard/View/MainView/KeyboardHelpView.swift

@@ -0,0 +1,127 @@
+//
+//  KeyboardHelpView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import UIKit
+
+protocol KeyboardHelpViewDelegate: NSObjectProtocol {
+    
+    func helpCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (() -> ()))
+}
+
+class KeyboardHelpView: KeyboardBaseView {
+    
+    struct UX {
+        static let cellWidth = (KeyboardConst.kb_kScreenW - 96.0) / 3.0
+        static let cellHeight = 46.0
+    }
+    
+    weak var helpDelegate: KeyboardHelpViewDelegate?
+    
+    var characterList: [CharacterModel]? {
+        didSet {
+            self.collectionView.reloadData()
+        }
+    }
+    
+    lazy var collectionView: UICollectionView = {
+            
+        let layout = UICollectionViewFlowLayout()
+        layout.minimumLineSpacing = 0
+        layout.scrollDirection = .vertical
+        
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.backgroundColor = .clear
+        
+        collectionView.dataSource = self
+        collectionView.delegate = self
+        
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.bounces = false
+        
+        collectionView.register(KeyboardCharacterCell.self, forCellWithReuseIdentifier: KeyboardCharacterCell.reuseIdentifier())
+        
+        return collectionView
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardHelpView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return (self.characterList?.count ?? 0) + 1
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        if indexPath.row == self.characterList?.count {
+            
+            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardCharacterCell.reuseIdentifier(), for: indexPath) as! KeyboardCharacterCell
+            cell.titleLabel.isHidden = true
+            cell.addBtn.isHidden = false
+            return cell
+        } else {
+            
+            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardCharacterCell.reuseIdentifier(), for: indexPath) as! KeyboardCharacterCell
+            if let model = self.characterList?[indexPath.row] {
+                cell.config(model: model)
+            }
+            return cell
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+        if self.dialogueLabel.text?.count == 0 {
+            self.toast(text: "请先粘贴文字")
+            return
+        }
+        
+        let cell = collectionView.cellForItem(at: indexPath) as! KeyboardCharacterCell
+        cell.setAnimate(play: true)
+        if let model = self.characterList?[indexPath.row], let characterId = model.id {
+            helpDelegate?.helpCollectionViewDidSelectItem(characterId: characterId, content: self.dialogueLabel.text ?? "") {
+                cell.setAnimate(play: false)
+                self.dialogueClearBtnAction()
+            }
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        
+        return CGSize(width: UX.cellWidth, height: UX.cellHeight)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+}
+
+extension KeyboardHelpView {
+    
+    func initUI() {
+        
+        self.addSubview(self.collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(deleteBtn.snp.left).offset(-6)
+            make.top.equalTo(dialogueView.snp.bottom).offset(6)
+            make.bottom.equalTo(self.sendBtn.snp.bottom).offset(3)
+        }
+    }
+}

+ 422 - 0
ios/AiKeyboard/View/MainView/KeyboardPrologueView.swift

@@ -0,0 +1,422 @@
+//
+//  KeyboardPrologueView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import UIKit
+import JXSegmentedView
+
+protocol KeyboardPrologueViewDelegate: NSObjectProtocol {
+    
+    func prologueCollectionViewDidSelectItem(name: String, complete: @escaping (([String]) -> ()))
+    
+    func prologueTableViewDidSelectItem(content: String)
+}
+
+class KeyboardPrologueView: KeyboardBaseView {
+    
+    struct UX {
+        static let twoTitleBtnWidth = (KeyboardConst.kb_kScreenW - 20) / 4
+        static let threeTitleBtnWidth = (KeyboardConst.kb_kScreenW - 20) / 3
+        static let fourTitleBtnWidth = (KeyboardConst.kb_kScreenW - 20) / 4
+        static let resultCellHeight = "单行文字".heightAccording(width: KeyboardConst.kb_kScreenW - 105, font: .systemFont(ofSize: 12), lineSpacing: 5) + 16
+    }
+    
+    weak var prologueDelegate: KeyboardPrologueViewDelegate?
+    
+    // 是否在加载
+    var isResultLoading: Bool = true
+    
+    var selectName: String?
+    
+    var resultList: [String]? {
+        didSet {
+            self.tableView.reloadData()
+        }
+    }
+    
+    // 开场白列表
+    var prologueList: [PrologueModel]? {
+        didSet {
+            updateUI()
+        }
+    }
+    
+    // 主题
+    var selectTopics: [PrologueTopicModel]? {
+        didSet {
+            self.collectionView.reloadData()
+        }
+    }
+    
+    let titleDataSource: JXSegmentedTitleDataSource = {
+        
+        let titleDataSource = JXSegmentedTitleDataSource()
+        titleDataSource.titles = []
+        titleDataSource.itemWidth = UX.fourTitleBtnWidth
+        titleDataSource.titleNormalColor = UIColor.hexStringColor(hexString: "#77849D")
+        titleDataSource.titleSelectedColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        titleDataSource.titleNormalFont = .systemFont(ofSize: 12)
+        titleDataSource.titleSelectedFont = .boldSystemFont(ofSize: 12)
+        titleDataSource.itemSpacing = 0
+        
+        return titleDataSource
+    }()
+    
+    lazy var pagingTitleView: JXSegmentedView = {
+        
+        let totalItemWidth = KeyboardConst.kb_kScreenW - 20
+        
+        let indicator = JXSegmentedIndicatorBackgroundView()
+        indicator.indicatorWidthIncrement = -4
+//        indicator.indicatorWidth = totalItemWidth / 2
+        indicator.indicatorHeight = 30
+        indicator.indicatorColor = .hexStringColor(hexString: "#DDCFFD")
+        indicator.indicatorCornerRadius = 15
+        
+        let titleView = JXSegmentedView(frame: CGRect(x: 0, y: 0, width: totalItemWidth, height: 34.0))
+        titleView.backgroundColor = .white
+        titleView.delegate = self
+        titleView.dataSource = titleDataSource
+        titleView.indicators = [indicator]
+//        titleView.listContainer = containerView
+        titleView.layer.cornerRadius = 17
+
+        return titleView
+    }()
+    
+    lazy var collectionView: UICollectionView = {
+            
+        let layout = UICollectionViewFlowLayout()
+        layout.minimumLineSpacing = 0
+        layout.scrollDirection = .vertical
+        
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.backgroundColor = .clear
+        
+        collectionView.dataSource = self
+        collectionView.delegate = self
+        
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.bounces = false
+        
+        collectionView.register(KeyboardCharacterCell.self, forCellWithReuseIdentifier: KeyboardCharacterCell.reuseIdentifier())
+        
+        return collectionView
+    }()
+    
+    lazy var resultView: UIView = {
+       
+        let view = UIView()
+        view.isHidden = true
+        return view
+    }()
+        
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.addTarget(self, action: #selector(backBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var regenerateBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.backgroundColor = .white
+        btn.layer.cornerRadius = 12
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.setTitle("重新生成", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 12)
+        btn.addTarget(self, action: #selector(regenerateBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var tableView: UITableView = {
+        
+        let tableView = UITableView(frame: .zero, style: .grouped)
+        tableView.backgroundColor = .clear
+        tableView.separatorStyle = .none
+        tableView.showsVerticalScrollIndicator = false
+        tableView.delegate = self
+        tableView.dataSource = self
+       
+        if #available(iOS 11, *) {
+            tableView.estimatedRowHeight = 0
+            tableView.estimatedSectionFooterHeight = 0
+            tableView.estimatedSectionHeaderHeight = 0
+            tableView.contentInsetAdjustmentBehavior = .never
+        }
+        
+        tableView.isUserInteractionEnabled = true
+        tableView.register(KeyboardTeachDialogueCell.self, forCellReuseIdentifier: KeyboardTeachDialogueCell.reuseIdentifier())
+        
+        return tableView
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardPrologueView {
+    
+    // 返回按钮点击
+    @objc func backBtnClickAction() {
+        
+        self.isResultLoading = true
+        self.collectionView.isHidden = false
+        self.resultView.isHidden = true
+        self.dialogueClearBtnAction()
+    }
+    
+    // 重新生成按钮点击
+    @objc func regenerateBtnClickAction() {
+        
+        self.isResultLoading = true
+        self.tableView.reloadData()
+        
+        if let selectName = self.selectName {
+            
+            prologueDelegate?.prologueCollectionViewDidSelectItem(name: selectName) { list in
+                self.isResultLoading = false
+                self.resultList = list
+            }
+        }
+    }
+}
+
+extension KeyboardPrologueView: JXSegmentedViewDelegate {
+    
+    //返回列表的数量
+    func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
+        return titleDataSource.titles.count
+    }
+    
+    func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
+        
+        if let prologueList = self.prologueList {
+            let topics = prologueList[index].topics
+            self.selectTopics = topics
+        }
+    }
+}
+
+extension KeyboardPrologueView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return self.selectTopics?.count ?? 0
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardCharacterCell.reuseIdentifier(), for: indexPath) as! KeyboardCharacterCell
+        if let selectTopics = self.selectTopics, selectTopics.count > 0 {
+            let name = selectTopics[indexPath.row].name
+            cell.config(content: name ?? "")
+        }
+        return cell
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+        if let selectTopics = self.selectTopics, selectTopics.count > 0 {
+            
+            // 清空回答列表
+            self.resultList?.removeAll()
+            self.collectionView.isHidden = true
+            self.resultView.isHidden = false
+            self.tableView.reloadData()
+            
+            let name = selectTopics[indexPath.row].name
+            prologueDelegate?.prologueCollectionViewDidSelectItem(name: name ?? "") { list in
+                self.isResultLoading = false
+                self.resultList = list
+            }
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        
+        let width = (KeyboardConst.kb_kScreenW - 96) / 3
+        return CGSize(width: width, height: 46)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+}
+
+extension KeyboardPrologueView: UITableViewDelegate, UITableViewDataSource {
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 1
+    }
+    
+    func numberOfSections(in tableView: UITableView) -> Int {
+        
+        return isResultLoading ? 4 : (resultList?.count ?? 0) + 1
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    
+        let cell = tableView.dequeueReusableCell(withIdentifier: KeyboardTeachDialogueCell.reuseIdentifier(), for: indexPath) as! KeyboardTeachDialogueCell
+        cell.backgroundColor = .clear
+        cell.selectionStyle = .none
+        if indexPath.section == 0 {
+            cell.isHidden = true
+        } else {
+            if let resultList = self.resultList, resultList.count > 0 && !isResultLoading {
+                let content = resultList[indexPath.section - 1]
+                cell.config(content: content)
+            }
+             
+            cell.setLoading(play: isResultLoading)
+        }
+        return cell
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        
+        if let resultList = self.resultList, resultList.count > 0 && !isResultLoading {
+            
+            let content = resultList[indexPath.section - 1]
+            prologueDelegate?.prologueTableViewDidSelectItem(content: content)
+            self.backBtnClickAction()
+        }
+    }
+    
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        
+        if isResultLoading {
+            return UX.resultCellHeight
+        }
+        
+        if indexPath.section != 0 {
+            if let resultList = self.resultList, resultList.count > 0 {
+                let content = resultList[indexPath.section - 1]
+                let height = content.heightAccording(width: KeyboardConst.kb_kScreenW - 105, font: .systemFont(ofSize: 12), lineSpacing: 5) + 16
+                return height
+            }
+        }
+        
+        return UX.resultCellHeight
+    }
+
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return 8
+    }
+    
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        let view = UIView(frame: CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW - 84, height: 8))
+        return view
+    }
+    
+    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+        return 0.001
+    }
+    
+    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+        return nil
+    }
+    
+}
+
+extension KeyboardPrologueView {
+    
+    func updateUI() {
+        
+        if let prologueList = self.prologueList {
+            
+            var titles = [String]()
+            for prologue in prologueList {
+                titles.append(prologue.title ?? "")
+            }
+            
+            if prologueList.count == 2 {
+                self.titleDataSource.itemWidth = UX.twoTitleBtnWidth
+            } else if prologueList.count == 3 {
+                self.titleDataSource.itemWidth = UX.threeTitleBtnWidth
+            } else {
+                self.titleDataSource.itemWidth = UX.fourTitleBtnWidth
+            }
+            
+            self.titleDataSource.titles = titles
+            self.pagingTitleView.dataSource = self.titleDataSource
+            self.pagingTitleView.reloadData()
+            
+            self.selectTopics = prologueList.first?.topics
+        }
+    }
+    
+    func initUI() {
+        
+        self.dialogueView.removeFromSuperview()
+        self.pasteBtn.removeFromSuperview()
+        
+        self.addSubview(pagingTitleView)
+        pagingTitleView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(-10)
+            make.height.equalTo(34)
+            make.top.equalTo(15)
+        }
+        
+        self.addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(deleteBtn.snp.left).offset(-6)
+            make.top.equalTo(pagingTitleView.snp.bottom).offset(14)
+            make.bottom.equalTo(0)
+        }
+        
+        deleteBtn.snp.remakeConstraints { make in
+            make.size.equalTo(CGSize(width: 58, height: 46))
+            make.right.equalTo(-10)
+            make.top.equalTo(pagingTitleView.snp.bottom).offset(14)
+        }
+        
+        self.addSubview(resultView)
+        resultView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(deleteBtn.snp.left).offset(-6)
+            make.top.equalTo(pagingTitleView.snp.bottom).offset(12)
+            make.bottom.equalTo(0)
+        }
+        
+        resultView.addSubview(tableView)
+        tableView.snp.makeConstraints { make in
+            make.top.left.bottom.right.equalTo(0)
+        }
+        
+        resultView.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(0)
+            make.top.equalTo(0)
+        }
+        
+        resultView.addSubview(regenerateBtn)
+        regenerateBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 74, height: 32))
+            make.top.right.equalTo(0)
+        }
+    }
+}

+ 342 - 0
ios/AiKeyboard/View/MainView/KeyboardTeachView.swift

@@ -0,0 +1,342 @@
+//
+//  KeyboardTeachView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/24.
+//
+
+import UIKit
+
+protocol KeyboardTeachViewDelegate: NSObjectProtocol {
+    
+    func teachCollectionViewDidSelectItem(characterId: String, content: String, complete: @escaping (([String]) -> ()))
+    
+    func teachTableViewDidSelectItem(content: String)
+}
+
+class KeyboardTeachView: KeyboardBaseView {
+    
+    struct UX {
+        static let cellWidth = (KeyboardConst.kb_kScreenW - 96.0) / 3.0
+        static let cellHeight = 46.0
+        static let resultCellHeight = "单行文字".heightAccording(width: KeyboardConst.kb_kScreenW - 105, font: .systemFont(ofSize: 12), lineSpacing: 5) + 16
+    }
+    
+    weak var teachDelegate: KeyboardTeachViewDelegate?
+    
+    // 是否在加载
+    var isResultLoading: Bool = true
+    
+    var selectCharacter: CharacterModel?
+    
+    var characterList: [CharacterModel]? {
+        didSet {
+            self.collectionView.reloadData()
+        }
+    }
+    
+    var resultList: [String]? {
+        didSet {
+            self.tableView.reloadData()
+        }
+    }
+    
+    lazy var collectionView: UICollectionView = {
+            
+        let layout = UICollectionViewFlowLayout()
+        layout.minimumLineSpacing = 0
+        layout.scrollDirection = .vertical
+        
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.backgroundColor = .clear
+        
+        collectionView.dataSource = self
+        collectionView.delegate = self
+        
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.bounces = false
+        
+        collectionView.register(KeyboardCharacterCell.self, forCellWithReuseIdentifier: KeyboardCharacterCell.reuseIdentifier())
+        
+        return collectionView
+    }()
+    
+    lazy var resultView: UIView = {
+       
+        let view = UIView()
+        view.isHidden = true
+        return view
+    }()
+        
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.addTarget(self, action: #selector(backBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var regenerateBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.backgroundColor = .white
+        btn.layer.cornerRadius = 12
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.setTitle("重新生成", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 12)
+        btn.addTarget(self, action: #selector(regenerateBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var tableView: UITableView = {
+        
+        let tableView = UITableView(frame: .zero, style: .grouped)
+        tableView.backgroundColor = .clear
+        tableView.separatorStyle = .none
+        tableView.showsVerticalScrollIndicator = false
+        tableView.delegate = self
+        tableView.dataSource = self
+       
+        if #available(iOS 11, *) {
+            tableView.estimatedRowHeight = 0
+            tableView.estimatedSectionFooterHeight = 0
+            tableView.estimatedSectionHeaderHeight = 0
+            tableView.contentInsetAdjustmentBehavior = .never
+        }
+        
+        tableView.isUserInteractionEnabled = true
+        tableView.register(KeyboardTeachDialogueCell.self, forCellReuseIdentifier: KeyboardTeachDialogueCell.reuseIdentifier())
+        
+        return tableView
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardTeachView {
+    
+    // 返回按钮点击
+    @objc func backBtnClickAction() {
+        
+        self.isResultLoading = true
+        self.collectionView.isHidden = false
+        self.resultView.isHidden = true
+        self.dialogueClearBtnAction()
+    }
+    
+    // 重新生成按钮点击
+    @objc func regenerateBtnClickAction() {
+        
+        self.isResultLoading = true
+        self.tableView.reloadData()
+        
+        if let selectCharacter = self.selectCharacter, let characterId = selectCharacter.id {
+            
+            teachDelegate?.teachCollectionViewDidSelectItem(characterId: characterId, content: self.dialogueLabel.text ?? "") { list in
+                self.isResultLoading = false
+                self.resultList = list
+            }
+        }
+    }
+}
+
+extension KeyboardTeachView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return (self.characterList?.count ?? 0) + 1
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        if indexPath.row == self.characterList?.count {
+            
+            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardCharacterCell.reuseIdentifier(), for: indexPath) as! KeyboardCharacterCell
+            cell.titleLabel.isHidden = true
+            cell.addBtn.isHidden = false
+            return cell
+        } else {
+            
+            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardCharacterCell.reuseIdentifier(), for: indexPath) as! KeyboardCharacterCell
+            if let model = self.characterList?[indexPath.row] {
+                cell.config(model: model)
+            }
+            return cell
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+        if self.dialogueLabel.text?.count == 0 {
+            self.toast(text: "请先粘贴文字")
+            return
+        }
+        
+        if let model = self.characterList?[indexPath.row], let characterId = model.id {
+            
+            // 清空回答列表
+            self.resultList?.removeAll()
+            self.collectionView.isHidden = true
+            self.resultView.isHidden = false
+            self.tableView.reloadData()
+            
+            self.selectCharacter = model
+            teachDelegate?.teachCollectionViewDidSelectItem(characterId: characterId, content: self.dialogueLabel.text ?? "") { list in
+                self.isResultLoading = false
+                self.resultList = list
+            }
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        
+        return CGSize(width: UX.cellWidth, height: UX.cellHeight)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+        return 6
+    }
+}
+
+extension KeyboardTeachView: UITableViewDelegate, UITableViewDataSource {
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 1
+    }
+    
+    func numberOfSections(in tableView: UITableView) -> Int {
+        
+        return isResultLoading ? 4 : (resultList?.count ?? 0) + 1
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    
+        let cell = tableView.dequeueReusableCell(withIdentifier: KeyboardTeachDialogueCell.reuseIdentifier(), for: indexPath) as! KeyboardTeachDialogueCell
+        cell.backgroundColor = .clear
+        cell.selectionStyle = .none
+        if indexPath.section == 0 {
+            cell.isHidden = true
+        } else {
+            if let resultList = self.resultList, resultList.count > 0 && !isResultLoading {
+                let content = resultList[indexPath.section - 1]
+                cell.config(content: content)
+            }
+             
+            cell.setLoading(play: isResultLoading)
+        }
+        return cell
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        
+        if let resultList = self.resultList, resultList.count > 0 && !isResultLoading {
+            
+            let content = resultList[indexPath.section - 1]
+            teachDelegate?.teachTableViewDidSelectItem(content: content)
+            self.backBtnClickAction()
+        }
+    }
+    
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        
+        if isResultLoading {
+            return UX.resultCellHeight
+        }
+        
+        if indexPath.section != 0 {
+            if let resultList = self.resultList, resultList.count > 0 {
+                let content = resultList[indexPath.section - 1]
+                let height = content.heightAccording(width: KeyboardConst.kb_kScreenW - 105, font: .systemFont(ofSize: 12), lineSpacing: 5) + 16
+                return height
+            }
+        }
+        
+        return UX.resultCellHeight
+    }
+
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return 8
+    }
+    
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        let view = UIView(frame: CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW - 84, height: 8))
+        return view
+    }
+    
+    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+        return 0.001
+    }
+    
+    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+        return nil
+    }
+    
+}
+
+extension KeyboardTeachView {
+    
+    func setupUI() {
+        
+        let attr = NSMutableAttributedString()
+        let copyIcon = NSTextAttachment(image: UIImage(named: "keyboard_copy_icon")!)
+        copyIcon.bounds = CGRect(x: -4, y: -4, width: 18, height: 18)
+        let copyAttr = NSAttributedString(attachment: copyIcon)
+        attr.append(copyAttr)
+        
+        let titleText = NSMutableAttributedString(string: "输入/粘贴你想说的话,润色升华")
+        titleText.addAttributes([.font: UIFont.systemFont(ofSize: 14, weight: .medium), .foregroundColor: UIColor.hexStringColor(hexString: "#996DFF")], range: NSRange(location: 0, length: titleText.length))
+        attr.append(titleText)
+
+        self.dialogueTitleLabel.attributedText = attr
+        
+        self.addSubview(self.collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(deleteBtn.snp.left).offset(-6)
+            make.top.equalTo(dialogueView.snp.bottom).offset(6)
+            make.bottom.equalTo(self.sendBtn.snp.bottom).offset(3)
+        }
+        
+        self.addSubview(resultView)
+        resultView.snp.makeConstraints { make in
+            make.left.equalTo(10)
+            make.right.equalTo(deleteBtn.snp.left).offset(-6)
+            make.top.equalTo(dialogueView.snp.bottom).offset(12)
+            make.bottom.equalTo(0)
+        }
+        
+        resultView.addSubview(tableView)
+        tableView.snp.makeConstraints { make in
+            make.top.left.bottom.right.equalTo(0)
+        }
+        
+        resultView.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(0)
+            make.top.equalTo(0)
+        }
+        
+        resultView.addSubview(regenerateBtn)
+        regenerateBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 74, height: 32))
+            make.top.right.equalTo(0)
+        }
+    }
+}

+ 176 - 0
ios/AiKeyboard/View/PopView/KeyboardExchangeView.swift

@@ -0,0 +1,176 @@
+//
+//  KeyboardExchangeView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import UIKit
+
+protocol KeyboardExchangeViewDelegate: NSObjectProtocol {
+    
+    func exchangeViewBackClickAction()
+    
+    func exchangeViewSaveClickAction(keyboardList: [KeyboardModel], success: @escaping (() -> ()))
+}
+
+class KeyboardExchangeView: UIView {
+    
+    struct UX {
+        static let cellWidth = (KeyboardConst.kb_kScreenW - 42.0) / 3.0
+        static let cellHeight = 79.0
+    }
+    
+    weak var delegate: KeyboardExchangeViewDelegate?
+    
+    var keyboardList: [KeyboardModel]? {
+        didSet {
+            self.collectionView.reloadData()
+        }
+    }
+    
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.addTarget(self, action: #selector(backBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var collectionView: UICollectionView = {
+            
+        let layout = UICollectionViewFlowLayout()
+        layout.minimumLineSpacing = 0
+        layout.scrollDirection = .vertical
+        
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.backgroundColor = .clear
+        
+        collectionView.dataSource = self
+        collectionView.delegate = self
+        
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.bounces = false
+        
+        collectionView.register(KeyboardExchangeCell.self, forCellWithReuseIdentifier: KeyboardExchangeCell.reuseIdentifier())
+        
+        return collectionView
+    }()
+    
+    lazy var saveBtn: UIButton = {
+        
+        let btn = UIButton()
+        
+        if let image = UIImage.gradient([.hexStringColor(hexString: "#7D46FC"), .hexStringColor(hexString: "#BC87FF")], size: CGSize(width: 220, height: 40), radius: 22, locations: [0,1], direction: .horizontal) {
+            
+            btn.setBackgroundImage(image, for: .normal)
+        }
+        
+        btn.setTitle("保存", for: .normal)
+        btn.setTitleColor(.white, for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
+        
+        btn.addTarget(self, action: #selector(saveBtnClickAction), for: .touchUpInside)
+        
+        return btn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardExchangeView {
+    
+    @objc func backBtnClickAction() {
+        delegate?.exchangeViewBackClickAction()
+//        self.removeFromSuperview()
+    }
+    
+    @objc func saveBtnClickAction() {
+        
+        if let keyboardList = self.keyboardList {
+            delegate?.exchangeViewSaveClickAction(keyboardList: keyboardList) {
+//                self.removeFromSuperview()
+            }
+        }
+    }
+}
+
+extension KeyboardExchangeView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return self.keyboardList?.count ?? 0
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: KeyboardExchangeCell.reuseIdentifier(), for: indexPath) as! KeyboardExchangeCell
+        if let model = self.keyboardList?[indexPath.row] {
+            cell.config(model: model)
+        }
+        return cell
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+        for i in 0..<(self.keyboardList?.count ?? 0) {
+            self.keyboardList?[i].isChoose = false
+        }
+        self.keyboardList?[indexPath.row].isChoose = true
+        self.collectionView.reloadData()
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        
+        return CGSize(width: UX.cellWidth, height: UX.cellHeight)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+        return 8
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+        return 9
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
+        return CGSize(width: KeyboardConst.kb_kScreenW, height: 45)
+    }
+}
+
+extension KeyboardExchangeView {
+    
+    func initUI() {
+        
+        self.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.top.equalTo(15)
+            make.left.equalTo(12)
+        }
+        
+        self.addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.left.equalTo(12)
+            make.right.equalTo(-12)
+            make.top.equalTo(backBtn.snp.bottom).offset(12)
+            make.bottom.equalTo(0)
+        }
+        
+        self.addSubview(saveBtn)
+        saveBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 220, height: 40))
+            make.centerX.equalToSuperview()
+            make.bottom.equalToSuperview().offset(-12)
+        }
+    }
+}

+ 244 - 0
ios/AiKeyboard/View/PopView/KeyboardFunctionPopView.swift

@@ -0,0 +1,244 @@
+//
+//  KeyboardFunctionPopView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/23.
+//
+
+import UIKit
+
+class KeyboardFunctionPopView: UIView {
+    
+    var selectClosure: ((KeyboardType) -> Void)?
+    
+    var buttons = [UIButton]()
+    
+    var selectType: KeyboardType? {
+        didSet {
+            self.updateUI()
+        }
+    }
+    
+    lazy var contentView: UIView = {
+       
+        let view = UIView()
+        view.isHidden = true
+        view.backgroundColor = .white
+        view.layer.cornerRadius = 16
+        return view
+    }()
+    
+    lazy var selectFuncView: UIView = {
+       
+        let view = UIView()
+        
+        view.isUserInteractionEnabled = true
+        let tap = UITapGestureRecognizer(target: self, action: #selector(closeButtonTapped))
+        view.addGestureRecognizer(tap)
+        return view
+    }()
+    
+    lazy var selectFuncLabel: UILabel = {
+       
+        let label = UILabel()
+        label.text = "帮聊"
+        label.font = .boldSystemFont(ofSize: 12)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        return label
+    }()
+    
+    lazy var arrowIcon: UIImageView = {
+       
+        let icon = UIImageView()
+        icon.image = UIImage(named: "icon_arrow_up")
+        return icon
+    }()
+    
+    lazy var funcHelpBtn: UIButton = {
+       
+        let btn = UIButton()
+        btn.tag = 0
+        btn.isSelected = true
+        btn.setBackgroundImage(UIImage(named: "keyboard_func_select_bg"), for: .selected)
+        btn.setTitle("帮聊", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
+        btn.addTarget(self, action: #selector(functionBtnTapped(_:)), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var funcTeachBtn: UIButton = {
+       
+        let btn = UIButton()
+        btn.tag = 1
+        btn.setBackgroundImage(UIImage(named: "keyboard_func_select_bg"), for: .selected)
+        btn.setTitle("教你说", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = UIFont.systemFont(ofSize: 12)
+        btn.addTarget(self, action: #selector(functionBtnTapped(_:)), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var funcPrologueBtn: UIButton = {
+       
+        let btn = UIButton()
+        btn.tag = 2
+        btn.setBackgroundImage(UIImage(named: "keyboard_func_select_bg"), for: .selected)
+        btn.setTitle("开场白", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = UIFont.systemFont(ofSize: 12)
+        btn.addTarget(self, action: #selector(functionBtnTapped(_:)), for: .touchUpInside)
+        return btn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setUI()
+        setupTapGesture()
+        
+        buttons = [funcHelpBtn, funcTeachBtn, funcPrologueBtn]
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    class func show(view: UIView, selectType: KeyboardType, selectClosure: @escaping (KeyboardType) -> Void) {
+        
+        let window = KeyboardFunctionPopView(frame: CGRect(x: 0, y: 0, width: KeyboardConst.kb_kScreenW, height: KeyboardConst.kb_kScreenH))
+        window.selectClosure = selectClosure
+        window.selectType = selectType
+        view.addSubview(window)
+        window.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.95, initialSpringVelocity: 0.05) {
+            window.contentView.isHidden = false
+        }
+    }
+}
+
+extension KeyboardFunctionPopView {
+    
+    @objc func closeButtonTapped() {
+        self.removeFromSuperview()
+    }
+    
+    @objc func functionBtnTapped(_ sender: UIButton) {
+        
+        for button in buttons {
+            button.isSelected = false
+        }
+        sender.isSelected = true
+        self.selectClosure?(KeyboardType(rawValue: sender.tag) ?? .teach)
+        self.closeButtonTapped()
+    }
+}
+
+extension KeyboardFunctionPopView: UIGestureRecognizerDelegate {
+    
+    // 添加点击手势识别器
+    private func setupTapGesture() {
+        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap(_:)))
+        tapGesture.delegate = self
+        self.addGestureRecognizer(tapGesture)
+    }
+    
+    // MARK: - Actions
+    @objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) {
+        let location = gesture.location(in: self)
+        if !contentView.frame.contains(location) {
+            self.closeButtonTapped()
+        }
+    }
+    
+    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
+        // 确保点击事件不是发生在containerView上
+        let location = touch.location(in: self)
+        return !contentView.frame.contains(location)
+    }
+}
+
+extension KeyboardFunctionPopView {
+    
+    func updateUI() {
+        
+        switch self.selectType {
+        case .help:
+            self.selectFuncLabel.text = "帮聊"
+            break
+        case .teach:
+            self.selectFuncLabel.text = "教你说"
+            break
+        case .prologue:
+            self.selectFuncLabel.text = "开场白"
+            break
+        default:
+            break
+        }
+        
+        for button in buttons {
+            if button.tag == self.selectType?.rawValue ?? 0 {
+                button.isSelected = true
+                button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
+            } else {
+                button.isSelected = false
+                button.titleLabel?.font = UIFont.systemFont(ofSize: 12)
+            }
+        }
+    }
+    
+    func setUI() {
+        
+        self.addSubview(contentView)
+        contentView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 85, height: 162))
+            make.left.equalTo(55)
+            make.top.equalTo(17)
+        }
+        
+        contentView.addSubview(selectFuncView)
+        selectFuncView.snp.makeConstraints { make in
+            make.left.top.right.equalTo(0)
+            make.height.equalTo(32)
+        }
+        
+        selectFuncView.addSubview(selectFuncLabel)
+        selectFuncLabel.snp.makeConstraints { make in
+            make.left.equalTo(12)
+            make.centerY.equalToSuperview()
+        }
+        
+        selectFuncView.addSubview(arrowIcon)
+        arrowIcon.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 18, height: 18))
+            make.centerY.equalToSuperview()
+            make.right.equalTo(-5)
+        }
+        
+        contentView.addSubview(funcHelpBtn)
+        funcHelpBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 81, height: 32))
+            make.left.equalTo(2)
+            make.right.equalTo(-2)
+            make.top.equalTo(selectFuncView.snp.bottom).offset(9)
+        }
+        
+        contentView.addSubview(funcTeachBtn)
+        funcTeachBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 81, height: 32))
+            make.left.equalTo(2)
+            make.right.equalTo(-2)
+            make.top.equalTo(funcHelpBtn.snp.bottom).offset(8)
+        }
+        
+        contentView.addSubview(funcPrologueBtn)
+        funcPrologueBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 81, height: 32))
+            make.left.equalTo(2)
+            make.right.equalTo(-2)
+            make.top.equalTo(funcTeachBtn.snp.bottom).offset(8)
+        }
+    }
+}

+ 105 - 0
ios/AiKeyboard/View/PopView/KeyboardLoginTipView.swift

@@ -0,0 +1,105 @@
+//
+//  KeyboardLoginTipView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import UIKit
+
+class KeyboardLoginTipView: UIView {
+    
+    lazy var bgImageView: UIImageView = {
+       
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "keyboard_bg")
+        return imageView
+    }()
+    
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        return btn
+    }()
+    
+    lazy var logoIcon: UIImageView = {
+       
+        let icon = UIImageView()
+        icon.image = UIImage(named: "keyboard_logo")
+        return icon
+    }()
+    
+    lazy var loginLabel: UILabel = {
+       
+        let label = UILabel()
+        label.text = "请登录后使用"
+        label.font = .systemFont(ofSize: 20, weight: .medium)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        return label
+    }()
+    
+    lazy var loginBtn: UIButton = {
+       
+        let btn = UIButton()
+        btn.setTitle("去登录", for: .normal)
+        btn.setTitleColor(.white, for: .normal)
+        
+        if let image = UIImage.gradient([UIColor.hexStringColor(hexString: "#7D46FC"), UIColor.hexStringColor(hexString: "#F5FAFF")], size: CGSize(width: 300, height: 48), locations: [0, 1], direction: .horizontal) {
+            
+            btn.backgroundColor = UIColor(patternImage: image)
+        }
+        
+        return btn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardLoginTipView {
+    
+    func initUI() {
+        
+        self.addSubview(bgImageView)
+        bgImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        self.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(12)
+            make.top.equalTo(14)
+        }
+        
+        self.addSubview(logoIcon)
+        logoIcon.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 83, height: 83))
+            make.centerX.equalToSuperview()
+            make.top.equalTo(70)
+        }
+        
+        self.addSubview(loginLabel)
+        loginLabel.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(logoIcon.snp.bottom).offset(14)
+        }
+        
+        self.addSubview(loginBtn)
+        loginBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 300, height: 48))
+            make.top.equalTo(loginLabel.snp.bottom).offset(29)
+            make.centerX.equalToSuperview()
+        }
+    }
+}

+ 127 - 0
ios/AiKeyboard/View/PopView/KeyboardMenuView.swift

@@ -0,0 +1,127 @@
+//
+//  KeyboardMenuView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/28.
+//
+
+import UIKit
+
+protocol KeyboardMenuViewDelegate: NSObjectProtocol {
+    
+    func menuBackBtnClickAction()
+}
+
+class KeyboardMenuView: UIView {
+    
+    struct UX {
+        static let btnWidth = (KeyboardConst.kb_kScreenW - 36.0) / 2.0
+        static let btnHeight = 74.0
+    }
+    
+    weak var delegate: KeyboardMenuViewDelegate?
+    
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.addTarget(self, action: #selector(backBtnClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var diyCharacterBtn: UIButton = {
+       
+        let btn = UIButton(frame: CGRect(x: 0, y: 0, width: UX.btnWidth, height: UX.btnHeight))
+        btn.backgroundColor = .white
+        btn.layer.cornerRadius = 14
+        btn.setTitle("定制人设", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
+        btn.titleLabel?.textAlignment = .center
+        btn.setImage(UIImage(named: "keyboard_menu_diy"), for: .normal)
+        btn.setImageTitleLayout(.imgTop, spacing: 8)
+        return btn
+    }()
+    
+    lazy var marketBtn: UIButton = {
+        
+        let btn = UIButton(frame: CGRect(x: 0, y: 0, width: UX.btnWidth, height: UX.btnHeight))
+        btn.backgroundColor = .white
+        btn.layer.cornerRadius = 14
+        btn.setTitle("人设市场", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
+        btn.titleLabel?.textAlignment = .center
+        btn.setImage(UIImage(named: "keyboard_menu_market"), for: .normal)
+        btn.setImageTitleLayout(.imgTop, spacing: 8)
+        return btn
+    }()
+    
+    lazy var vipBtn: UIButton = {
+        
+        let btn = UIButton(frame: CGRect(x: 0, y: 0, width: UX.btnWidth, height: UX.btnHeight))
+        btn.backgroundColor = .white
+        btn.layer.cornerRadius = 14
+        btn.setTitle("解锁会员", for: .normal)
+        btn.setTitleColor(.hexStringColor(hexString: "#000000", alpha: 0.8), for: .normal)
+        btn.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
+        btn.titleLabel?.textAlignment = .center
+        btn.setImage(UIImage(named: "keyboard_menu_vip"), for: .normal)
+        btn.setImageTitleLayout(.imgTop, spacing: 8)
+        return btn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+extension KeyboardMenuView {
+    
+    @objc func backBtnClickAction() {
+        self.removeFromSuperview()
+        delegate?.menuBackBtnClickAction()
+    }
+}
+
+extension KeyboardMenuView {
+    
+    func initUI() {
+        
+        self.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(12)
+            make.top.equalTo(16)
+        }
+        
+        self.addSubview(diyCharacterBtn)
+        diyCharacterBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: UX.btnWidth, height: UX.btnHeight))
+            make.top.equalTo(60)
+            make.left.equalTo(12)
+        }
+        
+        self.addSubview(marketBtn)
+        marketBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: UX.btnWidth, height: UX.btnHeight))
+            make.top.equalTo(60)
+            make.right.equalTo(-12)
+        }
+        
+        self.addSubview(vipBtn)
+        vipBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: UX.btnWidth, height: UX.btnHeight))
+            make.top.equalTo(diyCharacterBtn.snp.bottom).offset(12)
+            make.left.equalTo(12)
+        }
+    }
+}

+ 153 - 0
ios/AiKeyboard/View/PopView/KeyboardVipTipView.swift

@@ -0,0 +1,153 @@
+//
+//  KeyboardVipTipView.swift
+//  AiKeyboard
+//
+//  Created by Destiny on 2025/4/25.
+//
+
+import UIKit
+import Lottie
+
+class KeyboardVipTipView: UIView {
+    
+    lazy var bgImageView: UIImageView = {
+        
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "keyboard_bg")
+        return imageView
+    }()
+    
+    lazy var backBtn: UIButton = {
+        
+        let btn = UIButton()
+        btn.setImage(UIImage(named: "keyboard_back_btn"), for: .normal)
+        btn.layer.shadowOffset = CGSize(width: 0, height: 0)
+        btn.layer.shadowColor = UIColor.hexStringColor(hexString: "#000000", alpha: 0.12).cgColor
+        btn.layer.shadowRadius = 6.1
+        btn.addTarget(self, action: #selector(backButtonClickAction), for: .touchUpInside)
+        return btn
+    }()
+    
+    lazy var topImageView: UIImageView = {
+        
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "keyboard_vip_top")
+        return imageView
+    }()
+    
+    lazy var mainView: UIView = {
+      
+        let view = UIView()
+        view.backgroundColor = .white
+        view.layer.cornerRadius = 20
+        return view
+    }()
+    
+    lazy var mainLabel: UILabel = {
+       
+        let label = UILabel()
+        label.numberOfLines = 2
+        label.text = "您的免费提问次数已用完\n可订阅会员解锁无线提问"
+        label.font = .systemFont(ofSize: 14, weight: .medium)
+        label.textColor = .hexStringColor(hexString: "#000000", alpha: 0.8)
+        return label
+    }()
+    
+    lazy var unlockBtnAnimation: LottieAnimationView = {
+       
+        let animate = LottieAnimationView(name: "vip_button")
+        animate.loopMode = .loop
+        animate.backgroundColor = .clear
+        return animate
+    }()
+    
+    lazy var unlockLabel: UILabel = {
+        
+        let name = self.getFontPostScriptName(from: "淘宝买菜体.ttf")
+        let label = UILabel()
+        label.text = "¥0.1/天解锁"
+        label.font = UIFont(name: name ?? "", size: 24)
+        label.textColor = .white
+        return label
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        setUI()
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func getFontPostScriptName(from fontFile: String) -> String? {
+        guard let path = Bundle.main.path(forResource: fontFile, ofType: nil) else {
+            return nil
+        }
+        let fontURL = URL(fileURLWithPath: path)
+        guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL),
+              let font = CGFont(fontDataProvider) else {
+            return nil
+        }
+        return font.postScriptName as String?
+    }
+}
+
+extension KeyboardVipTipView {
+    
+    @objc func backButtonClickAction() {
+        
+        self.removeFromSuperview()
+    }
+}
+
+extension KeyboardVipTipView {
+    
+    func setUI() {
+        
+        self.addSubview(bgImageView)
+        bgImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        self.addSubview(backBtn)
+        backBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 32, height: 32))
+            make.left.equalTo(12)
+            make.top.equalTo(14)
+        }
+        
+        self.addSubview(topImageView)
+        topImageView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 264, height: 98.5))
+            make.top.equalTo(47)
+            make.centerX.equalToSuperview()
+        }
+        
+        self.addSubview(mainView)
+        mainView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 285, height: 122))
+            make.centerX.equalToSuperview()
+            make.top.equalTo(topImageView.snp.bottom).offset(-9)
+        }
+        
+        mainView.addSubview(mainLabel)
+        mainLabel.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(21)
+        }
+        
+        self.addSubview(unlockBtnAnimation)
+        unlockBtnAnimation.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 300, height: 48))
+            make.centerX.equalToSuperview()
+            make.top.equalTo(mainView.snp.bottom).offset(-30)
+        }
+        unlockBtnAnimation.play()
+        
+        unlockBtnAnimation.addSubview(unlockLabel)
+        unlockLabel.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+        }
+    }
+}

BIN
ios/AiKeyboard/淘宝买菜体.ttf


+ 1 - 0
ios/Flutter/Debug.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"

+ 1 - 0
ios/Flutter/Release.xcconfig

@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"

+ 0 - 0
ios/KeyboardSharedDataManager.swift


Some files were not shown because too many files changed in this diff