mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
feat: fix several view model issue (#13388)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Error messages in chat cells are now clearly displayed with improved formatting and dynamic height adjustment for better readability. * Introduced the ability to remove specific chat cell view models from a session. * **Bug Fixes** * Enhanced error handling to automatically remove invalid chat cell view models when a message creation fails. * **Other Improvements** * Improved internal logic for handling message attachments and added more detailed debug logging for the copilot response lifecycle. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -160,6 +160,7 @@ private extension ChatManager {
|
|||||||
viewModelId: UUID
|
viewModelId: UUID
|
||||||
) {
|
) {
|
||||||
assert(!Thread.isMainThread)
|
assert(!Thread.isMainThread)
|
||||||
|
print("[+] starting copilot response for session: \(sessionId)")
|
||||||
|
|
||||||
let messageParameters: [String: AnyHashable] = [
|
let messageParameters: [String: AnyHashable] = [
|
||||||
// packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx
|
// packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx
|
||||||
@@ -167,11 +168,11 @@ private extension ChatManager {
|
|||||||
"files": [String](), // attachment in context, keep nil for now
|
"files": [String](), // attachment in context, keep nil for now
|
||||||
"searchMode": editorData.isSearchEnabled ? "MUST" : "AUTO",
|
"searchMode": editorData.isSearchEnabled ? "MUST" : "AUTO",
|
||||||
]
|
]
|
||||||
let hasMultipleAttachmentBlobs = [
|
let attachmentCount = [
|
||||||
editorData.fileAttachments.count,
|
editorData.fileAttachments.count,
|
||||||
editorData.documentAttachments.count,
|
editorData.documentAttachments.count,
|
||||||
].reduce(0, +) > 1
|
].reduce(0, +)
|
||||||
let attachmentFieldName = hasMultipleAttachmentBlobs ? "options.blobs" : "options.blob"
|
let attachmentFieldName = attachmentCount > 1 && attachmentCount != 0 ? "options.blobs" : "options.blob"
|
||||||
let uploadableAttachments: [GraphQLFile] = [
|
let uploadableAttachments: [GraphQLFile] = [
|
||||||
editorData.fileAttachments.map { file -> GraphQLFile in
|
editorData.fileAttachments.map { file -> GraphQLFile in
|
||||||
.init(fieldName: attachmentFieldName, originalName: file.name, data: file.data ?? .init())
|
.init(fieldName: attachmentFieldName, originalName: file.name, data: file.data ?? .init())
|
||||||
@@ -183,8 +184,8 @@ private extension ChatManager {
|
|||||||
assert(uploadableAttachments.allSatisfy { !($0.data?.isEmpty ?? true) })
|
assert(uploadableAttachments.allSatisfy { !($0.data?.isEmpty ?? true) })
|
||||||
guard let input = try? CreateChatMessageInput(
|
guard let input = try? CreateChatMessageInput(
|
||||||
attachments: [],
|
attachments: [],
|
||||||
blob: hasMultipleAttachmentBlobs ? .none : "",
|
blob: .none,
|
||||||
blobs: hasMultipleAttachmentBlobs ? .some([]) : .none,
|
blobs: attachmentCount > 1 && attachmentCount != 0 ? .some([]) : .none,
|
||||||
content: .some(contextSnippet.isEmpty ? editorData.text : "\(contextSnippet)\n\(editorData.text)"),
|
content: .some(contextSnippet.isEmpty ? editorData.text : "\(contextSnippet)\n\(editorData.text)"),
|
||||||
params: .some(AffineGraphQL.JSON(_jsonValue: messageParameters)),
|
params: .some(AffineGraphQL.JSON(_jsonValue: messageParameters)),
|
||||||
sessionId: sessionId
|
sessionId: sessionId
|
||||||
@@ -195,11 +196,13 @@ private extension ChatManager {
|
|||||||
}
|
}
|
||||||
let mutation = CreateCopilotMessageMutation(options: input)
|
let mutation = CreateCopilotMessageMutation(options: input)
|
||||||
QLService.shared.client.upload(operation: mutation, files: uploadableAttachments) { result in
|
QLService.shared.client.upload(operation: mutation, files: uploadableAttachments) { result in
|
||||||
|
print("[*] createCopilotMessage result: \(result)")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result {
|
switch result {
|
||||||
case let .success(graphQLResult):
|
case let .success(graphQLResult):
|
||||||
guard let messageIdentifier = graphQLResult.data?.createCopilotMessage else {
|
guard let messageIdentifier = graphQLResult.data?.createCopilotMessage else {
|
||||||
self.report(sessionId, ChatError.invalidResponse)
|
self.report(sessionId, ChatError.invalidResponse)
|
||||||
|
self.delete(sessionId: sessionId, vmId: viewModelId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.startStreamingResponse(
|
self.startStreamingResponse(
|
||||||
@@ -217,6 +220,7 @@ private extension ChatManager {
|
|||||||
|
|
||||||
private extension ChatManager {
|
private extension ChatManager {
|
||||||
func startStreamingResponse(sessionId: String, messageId: String, applyingTo vmId: UUID) {
|
func startStreamingResponse(sessionId: String, messageId: String, applyingTo vmId: UUID) {
|
||||||
|
print("[+] starting streaming response for session: \(sessionId), message: \(messageId)")
|
||||||
let base = IntelligentContext.shared.webViewMetadata[.currentServerBaseUrl] as? String
|
let base = IntelligentContext.shared.webViewMetadata[.currentServerBaseUrl] as? String
|
||||||
guard let base, let url = URL(string: base) else {
|
guard let base, let url = URL(string: base) else {
|
||||||
report(sessionId, ChatError.invalidServerConfiguration)
|
report(sessionId, ChatError.invalidServerConfiguration)
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ public class ChatManager: ObservableObject, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func delete(sessionId: String, vmId: UUID) {
|
||||||
|
with(sessionId: sessionId) { $0.removeValue(forKey: vmId) }
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func append(sessionId: String, _ viewModel: any ChatCellViewModel) -> UUID {
|
public func append(sessionId: String, _ viewModel: any ChatCellViewModel) -> UUID {
|
||||||
with(sessionId: sessionId) { $0.updateValue(viewModel, forKey: viewModel.id) }
|
with(sessionId: sessionId) { $0.updateValue(viewModel, forKey: viewModel.id) }
|
||||||
|
|||||||
@@ -11,24 +11,61 @@ import Then
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class ErrorCell: ChatBaseCell {
|
class ErrorCell: ChatBaseCell {
|
||||||
|
let label = UILabel()
|
||||||
|
|
||||||
override func prepareContentView(inside contentView: UIView) {
|
override func prepareContentView(inside contentView: UIView) {
|
||||||
super.prepareContentView(inside: contentView)
|
super.prepareContentView(inside: contentView)
|
||||||
|
contentView.addSubview(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
|
label.attributedText = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutContentView(bounds: CGRect) {
|
override func layoutContentView(bounds: CGRect) {
|
||||||
super.layoutContentView(bounds: bounds)
|
super.layoutContentView(bounds: bounds)
|
||||||
|
let width = bounds.width * 0.8
|
||||||
|
label.frame = .init(
|
||||||
|
x: (bounds.width - width) / 2,
|
||||||
|
y: 0,
|
||||||
|
width: width,
|
||||||
|
height: bounds.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func configure(with viewModel: any ChatCellViewModel) {
|
||||||
|
super.configure(with: viewModel)
|
||||||
|
guard let vm = viewModel as? ErrorCellViewModel else {
|
||||||
|
assertionFailure("Invalid view model type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
label.attributedText = Self.attributeText(for: vm.errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func attributeText(for text: String) -> NSAttributedString {
|
||||||
|
return .init(string: text, attributes: [
|
||||||
|
.font: UIFont.preferredFont(forTextStyle: .footnote),
|
||||||
|
.foregroundColor: UIColor.affineTextSecondary,
|
||||||
|
.paragraphStyle: NSMutableParagraphStyle().then {
|
||||||
|
$0.lineBreakMode = .byWordWrapping
|
||||||
|
$0.alignment = .center
|
||||||
|
}
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
override class func heightForContent(
|
override class func heightForContent(
|
||||||
for viewModel: any ChatCellViewModel,
|
for viewModel: any ChatCellViewModel,
|
||||||
width: CGFloat
|
width: CGFloat
|
||||||
) -> CGFloat {
|
) -> CGFloat {
|
||||||
_ = viewModel
|
let vm = viewModel as! ErrorCellViewModel
|
||||||
_ = width
|
let text = Self.attributeText(for: vm.errorMessage)
|
||||||
return 0
|
let boundingRect = text.boundingRect(
|
||||||
|
with: CGSize(width: width * 0.8, height: .greatestFiniteMagnitude),
|
||||||
|
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
||||||
|
context: nil
|
||||||
|
)
|
||||||
|
let boundingSize = boundingRect.size
|
||||||
|
return ceil(boundingSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user