From 4586e4a18f8a0b70e002d52d960928352d8124b1 Mon Sep 17 00:00:00 2001 From: Lakr <25259084+Lakr233@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:23:03 +0800 Subject: [PATCH] feat: adopt new backend api for attachment (#13336) ## Summary by CodeRabbit * **New Features** * Introduced a new query for applying document updates using AI, allowing merged markdown responses. * Added support for an optional file upload field when creating chat messages. * **Improvements** * Enhanced recent Copilot sessions query with pagination by adding an offset parameter. * Refined attachment handling in chat responses to better distinguish between single and multiple file uploads, improving reliability. * **Bug Fixes** * Minor update to error handling for clearer messaging. * **Chores** * Cleaned up and updated iOS project configuration files for improved build consistency. --- .../ios/App/App.xcodeproj/project.pbxproj | 8 ++- .../ApplyDocUpdatesQuery.graphql.swift | 54 +++++++++++++++++++ ...etCopilotRecentSessionsQuery.graphql.swift | 16 ++++-- .../CreateChatMessageInput.graphql.swift | 7 +++ .../ChatManager/ChatManager+Stream.swift | 17 +++--- .../IntelligentContext.swift | 2 +- packages/frontend/apps/ios/App/Podfile.lock | 12 ++--- 7 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/ApplyDocUpdatesQuery.graphql.swift diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index 3ac7d0a46..c700d53e2 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 */ @@ -90,6 +90,8 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ C45499AB2D140B5000E21978 /* NBStore */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); path = NBStore; sourceTree = ""; }; @@ -337,13 +339,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/AffineGraphQL/Sources/Operations/Queries/ApplyDocUpdatesQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/ApplyDocUpdatesQuery.graphql.swift new file mode 100644 index 000000000..13080d596 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/ApplyDocUpdatesQuery.graphql.swift @@ -0,0 +1,54 @@ +// @generated +// This file was automatically generated and should not be edited. + +@_exported import ApolloAPI + +public class ApplyDocUpdatesQuery: GraphQLQuery { + public static let operationName: String = "applyDocUpdates" + public static let operationDocument: ApolloAPI.OperationDocument = .init( + definition: .init( + #"query applyDocUpdates($workspaceId: String!, $docId: String!, $op: String!, $updates: String!) { applyDocUpdates( workspaceId: $workspaceId docId: $docId op: $op updates: $updates ) }"# + )) + + public var workspaceId: String + public var docId: String + public var op: String + public var updates: String + + public init( + workspaceId: String, + docId: String, + op: String, + updates: String + ) { + self.workspaceId = workspaceId + self.docId = docId + self.op = op + self.updates = updates + } + + public var __variables: Variables? { [ + "workspaceId": workspaceId, + "docId": docId, + "op": op, + "updates": updates + ] } + + 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("applyDocUpdates", String.self, arguments: [ + "workspaceId": .variable("workspaceId"), + "docId": .variable("docId"), + "op": .variable("op"), + "updates": .variable("updates") + ]), + ] } + + /// Apply updates to a doc using LLM and return the merged markdown. + public var applyDocUpdates: String { __data["applyDocUpdates"] } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotRecentSessionsQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotRecentSessionsQuery.graphql.swift index ca0f2f173..3de44adfa 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotRecentSessionsQuery.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/GetCopilotRecentSessionsQuery.graphql.swift @@ -7,24 +7,28 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery { public static let operationName: String = "getCopilotRecentSessions" public static let operationDocument: ApolloAPI.OperationDocument = .init( definition: .init( - #"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: $limit } options: { fork: false, sessionOrder: desc, withMessages: false } ) { __typename ...PaginatedCopilotChats } } } }"#, + #"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10, $offset: Int = 0) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: $limit, offset: $offset } options: { action: false, fork: false, sessionOrder: desc, withMessages: false } ) { __typename ...PaginatedCopilotChats } } } }"#, fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self] )) public var workspaceId: String public var limit: GraphQLNullable + public var offset: GraphQLNullable public init( workspaceId: String, - limit: GraphQLNullable = 10 + limit: GraphQLNullable = 10, + offset: GraphQLNullable = 0 ) { self.workspaceId = workspaceId self.limit = limit + self.offset = offset } public var __variables: Variables? { [ "workspaceId": workspaceId, - "limit": limit + "limit": limit, + "offset": offset ] } public struct Data: AffineGraphQL.SelectionSet { @@ -65,8 +69,12 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery { public static var __selections: [ApolloAPI.Selection] { [ .field("__typename", String.self), .field("chats", Chats.self, arguments: [ - "pagination": ["first": .variable("limit")], + "pagination": [ + "first": .variable("limit"), + "offset": .variable("offset") + ], "options": [ + "action": false, "fork": false, "sessionOrder": "desc", "withMessages": false diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatMessageInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatMessageInput.graphql.swift index 45f0868e7..9d57fe5fd 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatMessageInput.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/CreateChatMessageInput.graphql.swift @@ -12,6 +12,7 @@ public struct CreateChatMessageInput: InputObject { public init( attachments: GraphQLNullable<[String]> = nil, + blob: GraphQLNullable = nil, blobs: GraphQLNullable<[Upload]> = nil, content: GraphQLNullable = nil, params: GraphQLNullable = nil, @@ -19,6 +20,7 @@ public struct CreateChatMessageInput: InputObject { ) { __data = InputDict([ "attachments": attachments, + "blob": blob, "blobs": blobs, "content": content, "params": params, @@ -31,6 +33,11 @@ public struct CreateChatMessageInput: InputObject { set { __data["attachments"] = newValue } } + public var blob: GraphQLNullable { + get { __data["blob"] } + set { __data["blob"] = newValue } + } + public var blobs: GraphQLNullable<[Upload]> { get { __data["blobs"] } set { __data["blobs"] = newValue } diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/ChatManager/ChatManager+Stream.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/ChatManager/ChatManager+Stream.swift index e5c571ce4..d77af6585 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/ChatManager/ChatManager+Stream.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/ChatManager/ChatManager+Stream.swift @@ -167,8 +167,12 @@ private extension ChatManager { "files": [String](), // attachment in context, keep nil for now "searchMode": editorData.isSearchEnabled ? "MUST" : "AUTO", ] - let attachmentFieldName = "options.blobs" - var uploadableAttachments: [GraphQLFile] = [ + let hasMultipleAttachmentBlobs = [ + editorData.fileAttachments.count, + editorData.documentAttachments.count, + ].reduce(0, +) > 1 + let attachmentFieldName = hasMultipleAttachmentBlobs ? "options.blobs" : "options.blob" + let uploadableAttachments: [GraphQLFile] = [ editorData.fileAttachments.map { file -> GraphQLFile in .init(fieldName: attachmentFieldName, originalName: file.name, data: file.data ?? .init()) }, @@ -177,15 +181,10 @@ private extension ChatManager { }, ].flatMap(\.self) assert(uploadableAttachments.allSatisfy { !($0.data?.isEmpty ?? true) }) - // in Apollo, filed name is handled as attached object to field when there is only one attachment - // to use array on our server, we need to append a dummy attachment - // which is ignored if data is empty and name is empty - if uploadableAttachments.count == 1 { - uploadableAttachments.append(.init(fieldName: attachmentFieldName, originalName: "", data: .init())) - } guard let input = try? CreateChatMessageInput( attachments: [], - blobs: .some([]), // must have the placeholder + blob: hasMultipleAttachmentBlobs ? .none : "", + blobs: hasMultipleAttachmentBlobs ? .some([]) : .none, content: .some(contextSnippet.isEmpty ? editorData.text : "\(contextSnippet)\n\(editorData.text)"), params: .some(AffineGraphQL.JSON(_jsonValue: messageParameters)), sessionId: sessionId diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext.swift index 544704015..41606bb7a 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext.swift @@ -62,7 +62,7 @@ public class IntelligentContext { "Login required: \(reason)" case let .sessionCreationFailed(reason): "Session creation failed: \(reason)" - case let .featureClosed: + case .featureClosed: "Intelligent feature closed" } } diff --git a/packages/frontend/apps/ios/App/Podfile.lock b/packages/frontend/apps/ios/App/Podfile.lock index 87d06e4df..ef4328549 100644 --- a/packages/frontend/apps/ios/App/Podfile.lock +++ b/packages/frontend/apps/ios/App/Podfile.lock @@ -45,13 +45,13 @@ EXTERNAL SOURCES: :path: "../../../../../node_modules/capacitor-plugin-app-tracking-transparency" SPEC CHECKSUMS: - Capacitor: 106e7a4205f4618d582b886a975657c61179138d - CapacitorApp: d63334c052278caf5d81585d80b21905c6f93f39 - CapacitorBrowser: 081852cf532acf77b9d2953f3a88fe5b9711fb06 + Capacitor: 03bc7cbdde6a629a8b910a9d7d78c3cc7ed09ea7 + CapacitorApp: febecbb9582cb353aed037e18ec765141f880fe9 + CapacitorBrowser: 6299776d496e968505464884d565992faa20444a CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f - CapacitorHaptics: 70e47470fa1a6bd6338cd102552e3846b7f9a1b3 - CapacitorKeyboard: 969647d0ca2e5c737d7300088e2517aa832434e2 - CapacitorPluginAppTrackingTransparency: 2a2792623a5a72795f2e8f9ab3f1147573732fd8 + CapacitorHaptics: 1f1e17041f435d8ead9ff2a34edd592c6aa6a8d6 + CapacitorKeyboard: 09fd91dcde4f8a37313e7f11bde553ad1ed52036 + CapacitorPluginAppTrackingTransparency: 92ae9c1cfb5cf477753db9269689332a686f675a CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 PODFILE CHECKSUM: 2c1e4be82121f2d9724ecf7e31dd14e165aeb082