diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index 20e68e423c..44b56f612f 100644 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj +++ b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj @@ -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 = ""; }; @@ -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"; diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift index 76dd137cf9..eb76742bed 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.swift @@ -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"), ]), ] ) diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift index 0698ce2c02..a016245eb0 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/Controller/MainViewController/MainViewController.swift @@ -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() + 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)") } - } diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift index dbb20ae53f..050acf2c3f 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBox.swift @@ -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() + 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) } } diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift new file mode 100644 index 0000000000..d59faca7ff --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxViewModel.swift @@ -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() + + // 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 + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift index 369c707c1b..71a7b173e0 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift @@ -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 }