mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-04 19:15:33 +08:00
chroe: input box view model
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 77;
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -77,6 +77,8 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
C45499AB2D140B5000E21978 /* NBStore */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
path = NBStore;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -324,13 +326,9 @@
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AFFiNE/Pods-AFFiNE-frameworks.sh\"\n";
|
||||
|
||||
@@ -27,7 +27,7 @@ let package = Package(
|
||||
.product(name: "Apollo", package: "apollo-ios"),
|
||||
.product(name: "OrderedCollections", package: "swift-collections"),
|
||||
], resources: [
|
||||
.process("Interface/View/InputBox/InputBox.xcassets")
|
||||
.process("Interface/View/InputBox/InputBox.xcassets"),
|
||||
]),
|
||||
]
|
||||
)
|
||||
|
||||
+22
-13
@@ -1,3 +1,4 @@
|
||||
import Combine
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
@@ -13,6 +14,11 @@ class MainViewController: UIViewController {
|
||||
$0.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let intelligentContext = IntelligentContext.shared
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -72,27 +78,30 @@ extension MainViewController: MainHeaderViewDelegate {
|
||||
|
||||
extension MainViewController: InputBoxDelegate {
|
||||
func inputBoxDidTapAddAttachment() {
|
||||
|
||||
// TODO: 实现添加附件功能
|
||||
print("Add attachment tapped")
|
||||
}
|
||||
|
||||
|
||||
func inputBoxDidTapTool() {
|
||||
|
||||
print("Tool toggled: \(inputBox.viewModel.isToolEnabled)")
|
||||
}
|
||||
|
||||
|
||||
func inputBoxDidTapNetwork() {
|
||||
|
||||
print("Network toggled: \(inputBox.viewModel.isNetworkEnabled)")
|
||||
}
|
||||
|
||||
|
||||
func inputBoxDidTapDeepThinking() {
|
||||
|
||||
print("Deep thinking toggled: \(inputBox.viewModel.isDeepThinkingEnabled)")
|
||||
}
|
||||
|
||||
func inputBoxDidTapSend() {
|
||||
|
||||
|
||||
func inputBoxDidTapSend(data: InputBoxData) {
|
||||
// 处理发送逻辑
|
||||
guard !data.text.isEmpty else { return }
|
||||
print("[*] send tapped with text: \(data.text)")
|
||||
}
|
||||
|
||||
|
||||
func inputBoxTextDidChange(_ text: String) {
|
||||
|
||||
// 可以在这里处理文本变化的其他逻辑
|
||||
print("Text changed: \(text)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+64
-2
@@ -1,3 +1,4 @@
|
||||
import Combine
|
||||
import SnapKit
|
||||
import Then
|
||||
import UIKit
|
||||
@@ -7,13 +8,18 @@ protocol InputBoxDelegate: AnyObject {
|
||||
func inputBoxDidTapTool()
|
||||
func inputBoxDidTapNetwork()
|
||||
func inputBoxDidTapDeepThinking()
|
||||
func inputBoxDidTapSend()
|
||||
func inputBoxDidTapSend(data: InputBoxData)
|
||||
func inputBoxTextDidChange(_ text: String)
|
||||
}
|
||||
|
||||
class InputBox: UIView {
|
||||
weak var delegate: InputBoxDelegate?
|
||||
|
||||
// MARK: - ViewModel
|
||||
|
||||
public let viewModel = InputBoxViewModel()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private lazy var containerView = UIView().then {
|
||||
$0.backgroundColor = .systemBackground
|
||||
$0.layer.cornerRadius = 12
|
||||
@@ -136,6 +142,7 @@ class InputBox: UIView {
|
||||
super.init(frame: .zero)
|
||||
setupViews()
|
||||
setupConstraints()
|
||||
setupBindings()
|
||||
updatePlaceholderVisibility()
|
||||
}
|
||||
|
||||
@@ -151,6 +158,56 @@ class InputBox: UIView {
|
||||
containerView.addSubview(placeholderLabel)
|
||||
}
|
||||
|
||||
private func setupBindings() {
|
||||
// 绑定 ViewModel 到 UI
|
||||
viewModel.$inputText
|
||||
.sink { [weak self] text in
|
||||
if self?.textView.text != text {
|
||||
self?.textView.text = text
|
||||
self?.updatePlaceholderVisibility()
|
||||
self?.updateTextViewHeight()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isToolEnabled
|
||||
.sink { [weak self] enabled in
|
||||
self?.toolButton.isSelected = enabled
|
||||
self?.toolButton.tintColor = enabled ? .systemBlue : .secondaryLabel
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isNetworkEnabled
|
||||
.sink { [weak self] enabled in
|
||||
self?.webButton.isSelected = enabled
|
||||
self?.webButton.tintColor = enabled ? .systemBlue : .secondaryLabel
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isDeepThinkingEnabled
|
||||
.sink { [weak self] enabled in
|
||||
self?.reactButton.isSelected = enabled
|
||||
self?.reactButton.tintColor = enabled ? .systemBlue : .secondaryLabel
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$canSend
|
||||
.sink { [weak self] canSend in
|
||||
self?.sendButton.isEnabled = canSend
|
||||
self?.sendButton.alpha = canSend ? 1.0 : 0.5
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$isSending
|
||||
.sink { [weak self] isSending in
|
||||
self?.sendButton.isEnabled = !isSending
|
||||
if isSending {
|
||||
// TODO: 添加加载动画
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func setupConstraints() {
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(16)
|
||||
@@ -212,19 +269,23 @@ class InputBox: UIView {
|
||||
}
|
||||
|
||||
@objc private func toolButtonTapped() {
|
||||
viewModel.toggleTool()
|
||||
delegate?.inputBoxDidTapTool()
|
||||
}
|
||||
|
||||
@objc private func webButtonTapped() {
|
||||
viewModel.toggleNetwork()
|
||||
delegate?.inputBoxDidTapNetwork()
|
||||
}
|
||||
|
||||
@objc private func reactButtonTapped() {
|
||||
viewModel.toggleDeepThinking()
|
||||
delegate?.inputBoxDidTapDeepThinking()
|
||||
}
|
||||
|
||||
@objc private func sendButtonTapped() {
|
||||
delegate?.inputBoxDidTapSend()
|
||||
let data = viewModel.prepareSendData()
|
||||
delegate?.inputBoxDidTapSend(data: data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +295,7 @@ extension InputBox: UITextViewDelegate {
|
||||
func textViewDidChange(_ textView: UITextView) {
|
||||
updatePlaceholderVisibility()
|
||||
updateTextViewHeight()
|
||||
viewModel.updateText(textView.text)
|
||||
delegate?.inputBoxTextDidChange(textView.text)
|
||||
}
|
||||
}
|
||||
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// InputBoxViewModel.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by AI Assistant on 6/17/25.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
public class InputBoxViewModel: ObservableObject {
|
||||
// MARK: - Published Properties
|
||||
|
||||
@Published public var inputText: String = ""
|
||||
@Published public var isToolEnabled: Bool = false
|
||||
@Published public var isNetworkEnabled: Bool = false
|
||||
@Published public var isDeepThinkingEnabled: Bool = false
|
||||
@Published public var hasAttachments: Bool = false
|
||||
@Published public var attachments: [InputAttachment] = []
|
||||
@Published public var isSending: Bool = false
|
||||
|
||||
// MARK: - Private Properties
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init() {
|
||||
setupBindings()
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func setupBindings() {
|
||||
// 监听文本变化,自动更新发送按钮状态
|
||||
$inputText
|
||||
.map { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||
.assign(to: \.canSend, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
// 监听附件变化
|
||||
$attachments
|
||||
.map { !$0.isEmpty }
|
||||
.assign(to: \.hasAttachments, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public Properties
|
||||
|
||||
@Published public var canSend: Bool = false
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
public func updateText(_ text: String) {
|
||||
inputText = text
|
||||
}
|
||||
|
||||
public func toggleTool() {
|
||||
isToolEnabled.toggle()
|
||||
}
|
||||
|
||||
public func toggleNetwork() {
|
||||
isNetworkEnabled.toggle()
|
||||
}
|
||||
|
||||
public func toggleDeepThinking() {
|
||||
isDeepThinkingEnabled.toggle()
|
||||
}
|
||||
|
||||
public func addAttachment(_ attachment: InputAttachment) {
|
||||
attachments.append(attachment)
|
||||
}
|
||||
|
||||
public func removeAttachment(at index: Int) {
|
||||
guard index < attachments.count else { return }
|
||||
attachments.remove(at: index)
|
||||
}
|
||||
|
||||
public func clearAttachments() {
|
||||
attachments.removeAll()
|
||||
}
|
||||
|
||||
public func prepareSendData() -> InputBoxData {
|
||||
InputBoxData(
|
||||
text: inputText.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
attachments: attachments,
|
||||
isToolEnabled: isToolEnabled,
|
||||
isNetworkEnabled: isNetworkEnabled,
|
||||
isDeepThinkingEnabled: isDeepThinkingEnabled
|
||||
)
|
||||
}
|
||||
|
||||
public func resetInput() {
|
||||
inputText = ""
|
||||
attachments.removeAll()
|
||||
isToolEnabled = false
|
||||
isNetworkEnabled = false
|
||||
isDeepThinkingEnabled = false
|
||||
isSending = false
|
||||
}
|
||||
|
||||
public func setSending(_ sending: Bool) {
|
||||
isSending = sending
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Supporting Types
|
||||
|
||||
public struct InputBoxData {
|
||||
public let text: String
|
||||
public let attachments: [InputAttachment]
|
||||
public let isToolEnabled: Bool
|
||||
public let isNetworkEnabled: Bool
|
||||
public let isDeepThinkingEnabled: Bool
|
||||
|
||||
public init(
|
||||
text: String,
|
||||
attachments: [InputAttachment],
|
||||
isToolEnabled: Bool,
|
||||
isNetworkEnabled: Bool,
|
||||
isDeepThinkingEnabled: Bool
|
||||
) {
|
||||
self.text = text
|
||||
self.attachments = attachments
|
||||
self.isToolEnabled = isToolEnabled
|
||||
self.isNetworkEnabled = isNetworkEnabled
|
||||
self.isDeepThinkingEnabled = isDeepThinkingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public struct InputAttachment {
|
||||
public let id: String
|
||||
public let type: AttachmentType
|
||||
public let data: Data?
|
||||
public let url: URL?
|
||||
public let name: String
|
||||
public let size: Int64
|
||||
|
||||
public init(
|
||||
id: String = UUID().uuidString,
|
||||
type: AttachmentType,
|
||||
data: Data? = nil,
|
||||
url: URL? = nil,
|
||||
name: String,
|
||||
size: Int64 = 0
|
||||
) {
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.data = data
|
||||
self.url = url
|
||||
self.name = name
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
public enum AttachmentType {
|
||||
case image
|
||||
case document
|
||||
case video
|
||||
case audio
|
||||
case other(String)
|
||||
|
||||
public var displayName: String {
|
||||
switch self {
|
||||
case .image: "Image"
|
||||
case .document: "Document"
|
||||
case .video: "Video"
|
||||
case .audio: "Audio"
|
||||
case let .other(type): type
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -5,6 +5,7 @@
|
||||
// Created by 秋星桥 on 6/17/25.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
@@ -21,4 +22,6 @@ public class IntelligentContext {
|
||||
// TODO: if needed
|
||||
completion()
|
||||
}
|
||||
|
||||
// MARK: - Input Processing
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user