chore: added mime-type in gql (#13414)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved file and image attachment handling by including MIME type
information for uploads.
* Added a new query to fetch document summaries by workspace and
document IDs.

* **Refactor**
* Minor adjustments to method signatures and property initializations to
streamline code and maintain consistency.
* Updated access levels for certain properties and methods to internal,
enhancing encapsulation.

* **Style**
  * Formatting and whitespace clean-up for improved code readability.

No changes to user-facing functionality or behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Lakr
2025-08-05 16:10:40 +08:00
committed by GitHub
parent 36d58cd6c5
commit d398aa9a71
9 changed files with 118 additions and 19 deletions
@@ -9,9 +9,9 @@ import Intelligents
import UIKit import UIKit
extension AFFiNEViewController: IntelligentsButtonDelegate { extension AFFiNEViewController: IntelligentsButtonDelegate {
func onIntelligentsButtonTapped(_ button: IntelligentsButton) { func onIntelligentsButtonTapped(_: IntelligentsButton) {
// if it shows up then we are ready to go // if it shows up then we are ready to go
let controller = IntelligentsController() let controller = IntelligentsController()
self.present(controller, animated: true) present(controller, animated: true)
} }
} }
@@ -0,0 +1,74 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetDocSummaryQuery: GraphQLQuery {
public static let operationName: String = "getDocSummary"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getDocSummary($workspaceId: String!, $docId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $docId) { __typename summary } } }"#
))
public var workspaceId: String
public var docId: String
public init(
workspaceId: String,
docId: String
) {
self.workspaceId = workspaceId
self.docId = docId
}
public var __variables: Variables? { [
"workspaceId": workspaceId,
"docId": docId
] }
public struct Data: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
] }
/// Get workspace by id
public var workspace: Workspace { __data["workspace"] }
/// Workspace
///
/// Parent Type: `WorkspaceType`
public struct Workspace: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("doc", Doc.self, arguments: ["docId": .variable("docId")]),
] }
/// Get get with given id
public var doc: Doc { __data["doc"] }
/// Workspace.Doc
///
/// Parent Type: `DocType`
public struct Doc: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.DocType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("summary", String?.self),
] }
public var summary: String? { __data["summary"] }
}
}
}
}
@@ -12,6 +12,7 @@ import EventSource
import Foundation import Foundation
import MarkdownParser import MarkdownParser
import MarkdownView import MarkdownView
import UniformTypeIdentifiers
private let loadingIndicator = "" private let loadingIndicator = ""
@@ -175,16 +176,26 @@ private extension ChatManager {
let attachmentFieldName = attachmentCount > 1 && attachmentCount != 0 ? "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,
mimeType: mimeType(text: file.name),
data: file.data ?? .init()
)
}, },
editorData.imageAttachments.map { image -> GraphQLFile in editorData.imageAttachments.map { image -> GraphQLFile in
.init(fieldName: attachmentFieldName, originalName: "image.jpg", data: image.imageData) .init(
fieldName: attachmentFieldName,
originalName: "image.jpg",
mimeType: mimeType(pathExtension: "jpg"),
data: image.imageData
)
}, },
].flatMap(\.self) ].flatMap(\.self)
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: .none, blob: attachmentCount == 1 ? "" : .none,
blobs: attachmentCount > 1 && attachmentCount != 0 ? .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)),
@@ -216,6 +227,20 @@ private extension ChatManager {
} }
} }
} }
private func pathExtension(for text: String) -> String {
(text as NSString).pathExtension
}
private func mimeType(pathExtension: String) -> String {
let type = UTType(filenameExtension: pathExtension) ?? .data
return type.preferredMIMEType ?? "application/octet-stream"
}
private func mimeType(text: String) -> String {
let pathExt = pathExtension(for: text)
return mimeType(pathExtension: pathExt)
}
} }
private extension ChatManager { private extension ChatManager {
@@ -68,7 +68,7 @@ public class ChatManager: ObservableObject, @unchecked Sendable {
} }
} }
} }
public func delete(sessionId: String, vmId: UUID) { public func delete(sessionId: String, vmId: UUID) {
with(sessionId: sessionId) { $0.removeValue(forKey: vmId) } with(sessionId: sessionId) { $0.removeValue(forKey: vmId) }
} }
@@ -10,7 +10,7 @@ import Foundation
extension QLService { extension QLService {
final class URLSessionCookieClient: URLSessionClient { final class URLSessionCookieClient: URLSessionClient {
public init() { init() {
super.init() super.init()
session.configuration.httpCookieStorage = .init() session.configuration.httpCookieStorage = .init()
HTTPCookieStorage.shared.cookies?.forEach { cookie in HTTPCookieStorage.shared.cookies?.forEach { cookie in
@@ -12,7 +12,7 @@ import UIKit
class ErrorCell: ChatBaseCell { class ErrorCell: ChatBaseCell {
let label = UILabel() 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) contentView.addSubview(label)
@@ -33,7 +33,7 @@ class ErrorCell: ChatBaseCell {
height: bounds.height height: bounds.height
) )
} }
override func configure(with viewModel: any ChatCellViewModel) { override func configure(with viewModel: any ChatCellViewModel) {
super.configure(with: viewModel) super.configure(with: viewModel)
guard let vm = viewModel as? ErrorCellViewModel else { guard let vm = viewModel as? ErrorCellViewModel else {
@@ -44,16 +44,16 @@ class ErrorCell: ChatBaseCell {
} }
static func attributeText(for text: String) -> NSAttributedString { static func attributeText(for text: String) -> NSAttributedString {
return .init(string: text, attributes: [ .init(string: text, attributes: [
.font: UIFont.preferredFont(forTextStyle: .footnote), .font: UIFont.preferredFont(forTextStyle: .footnote),
.foregroundColor: UIColor.affineTextSecondary, .foregroundColor: UIColor.affineTextSecondary,
.paragraphStyle: NSMutableParagraphStyle().then { .paragraphStyle: NSMutableParagraphStyle().then {
$0.lineBreakMode = .byWordWrapping $0.lineBreakMode = .byWordWrapping
$0.alignment = .center $0.alignment = .center
} },
]) ])
} }
override class func heightForContent( override class func heightForContent(
for viewModel: any ChatCellViewModel, for viewModel: any ChatCellViewModel,
width: CGFloat width: CGFloat
@@ -6,7 +6,7 @@ import UIKit
class InputBox: UIView { class InputBox: UIView {
weak var delegate: InputBoxDelegate? weak var delegate: InputBoxDelegate?
public let viewModel = InputBoxViewModel() let viewModel = InputBoxViewModel()
var cancellables = Set<AnyCancellable>() var cancellables = Set<AnyCancellable>()
lazy var containerView = UIView().then { lazy var containerView = UIView().then {
@@ -257,7 +257,7 @@ class InputBox: UIView {
// MARK: - Public Methods // MARK: - Public Methods
public func addImageAttachment(_ image: UIImage) { func addImageAttachment(_ image: UIImage) {
let attachment = ImageAttachment(image: image) let attachment = ImageAttachment(image: image)
performWithAnimation { [self] in performWithAnimation { [self] in
@@ -266,7 +266,7 @@ class InputBox: UIView {
} }
} }
public func addFileAttachment(_ url: URL) throws { func addFileAttachment(_ url: URL) throws {
// check less then 15mb // check less then 15mb
let fileSizeLimit: Int64 = 15 * 1024 * 1024 // 15 MB let fileSizeLimit: Int64 = 15 * 1024 * 1024 // 15 MB
let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path) let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path)
@@ -292,14 +292,14 @@ class InputBox: UIView {
} }
} }
public func addDocumentAttachment(_ documentAttachment: DocumentAttachment) { func addDocumentAttachment(_ documentAttachment: DocumentAttachment) {
performWithAnimation { [self] in performWithAnimation { [self] in
viewModel.addDocumentAttachment(documentAttachment) viewModel.addDocumentAttachment(documentAttachment)
layoutIfNeeded() layoutIfNeeded()
} }
} }
public var inputBoxData: InputBoxData { var inputBoxData: InputBoxData {
viewModel.prepareSendData() viewModel.prepareSendData()
} }
} }
@@ -26,7 +26,7 @@ public class IntelligentsButton: UIView {
lazy var activityIndicator = UIActivityIndicatorView() lazy var activityIndicator = UIActivityIndicatorView()
public weak var delegate: (any IntelligentsButtonDelegate)? = nil { public weak var delegate: (any IntelligentsButtonDelegate)? {
didSet { assert(Thread.isMainThread) } didSet { assert(Thread.isMainThread) }
} }
@@ -20,7 +20,7 @@ class DarkActionButton: UIView {
let titleLabel = UILabel() let titleLabel = UILabel()
let iconView = UIImageView() let iconView = UIImageView()
var action: (() -> Void)? = nil var action: (() -> Void)?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)