diff --git a/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift b/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift index eedfaaa68c..f3e626ad7d 100644 --- a/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift +++ b/packages/frontend/apps/ios/App/App/AffineViewController+AIButton.swift @@ -13,7 +13,7 @@ extension AFFiNEViewController: IntelligentsButtonDelegate { IntelligentContext.shared.webView = webView! button.beginProgress() - IntelligentContext.shared.preparePresent() { + IntelligentContext.shared.preparePresent() { _ in button.stopProgress() let controller = IntelligentsController() self.present(controller, animated: true) diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/IndexerSearchDocsQuery.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/IndexerSearchDocsQuery.graphql.swift new file mode 100644 index 0000000000..b49aa51bdf --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Operations/Queries/IndexerSearchDocsQuery.graphql.swift @@ -0,0 +1,128 @@ +// @generated +// This file was automatically generated and should not be edited. + +@_exported import ApolloAPI + +public class IndexerSearchDocsQuery: GraphQLQuery { + public static let operationName: String = "indexerSearchDocs" + public static let operationDocument: ApolloAPI.OperationDocument = .init( + definition: .init( + #"query indexerSearchDocs($id: String!, $input: SearchDocsInput!) { workspace(id: $id) { __typename searchDocs(input: $input) { __typename docId title blockId highlight createdAt updatedAt createdByUser { __typename id name avatarUrl } updatedByUser { __typename id name avatarUrl } } } }"# + )) + + public var id: String + public var input: SearchDocsInput + + public init( + id: String, + input: SearchDocsInput + ) { + self.id = id + self.input = input + } + + public var __variables: Variables? { [ + "id": id, + "input": input + ] } + + 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("id")]), + ] } + + /// 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("searchDocs", [SearchDoc].self, arguments: ["input": .variable("input")]), + ] } + + /// Search docs by keyword + public var searchDocs: [SearchDoc] { __data["searchDocs"] } + + /// Workspace.SearchDoc + /// + /// Parent Type: `SearchDocObjectType` + public struct SearchDoc: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.SearchDocObjectType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("docId", String.self), + .field("title", String.self), + .field("blockId", String.self), + .field("highlight", String.self), + .field("createdAt", AffineGraphQL.DateTime.self), + .field("updatedAt", AffineGraphQL.DateTime.self), + .field("createdByUser", CreatedByUser?.self), + .field("updatedByUser", UpdatedByUser?.self), + ] } + + public var docId: String { __data["docId"] } + public var title: String { __data["title"] } + public var blockId: String { __data["blockId"] } + public var highlight: String { __data["highlight"] } + public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] } + public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] } + public var createdByUser: CreatedByUser? { __data["createdByUser"] } + public var updatedByUser: UpdatedByUser? { __data["updatedByUser"] } + + /// Workspace.SearchDoc.CreatedByUser + /// + /// Parent Type: `PublicUserType` + public struct CreatedByUser: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PublicUserType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("id", String.self), + .field("name", String.self), + .field("avatarUrl", String?.self), + ] } + + public var id: String { __data["id"] } + public var name: String { __data["name"] } + public var avatarUrl: String? { __data["avatarUrl"] } + } + + /// Workspace.SearchDoc.UpdatedByUser + /// + /// Parent Type: `PublicUserType` + public struct UpdatedByUser: AffineGraphQL.SelectionSet { + public let __data: DataDict + public init(_dataDict: DataDict) { __data = _dataDict } + + public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PublicUserType } + public static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("id", String.self), + .field("name", String.self), + .field("avatarUrl", String?.self), + ] } + + public var id: String { __data["id"] } + public var name: String { __data["name"] } + public var avatarUrl: String? { __data["avatarUrl"] } + } + } + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatHistoriesInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatHistoriesInput.graphql.swift index 4c102e06a6..b8196af5a1 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatHistoriesInput.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatHistoriesInput.graphql.swift @@ -15,6 +15,7 @@ public struct QueryChatHistoriesInput: InputObject { fork: GraphQLNullable = nil, limit: GraphQLNullable = nil, messageOrder: GraphQLNullable> = nil, + pinned: GraphQLNullable = nil, sessionId: GraphQLNullable = nil, sessionOrder: GraphQLNullable> = nil, skip: GraphQLNullable = nil, @@ -25,6 +26,7 @@ public struct QueryChatHistoriesInput: InputObject { "fork": fork, "limit": limit, "messageOrder": messageOrder, + "pinned": pinned, "sessionId": sessionId, "sessionOrder": sessionOrder, "skip": skip, @@ -52,6 +54,11 @@ public struct QueryChatHistoriesInput: InputObject { set { __data["messageOrder"] = newValue } } + public var pinned: GraphQLNullable { + get { __data["pinned"] } + set { __data["pinned"] = newValue } + } + public var sessionId: GraphQLNullable { get { __data["sessionId"] } set { __data["sessionId"] = newValue } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatSessionsInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatSessionsInput.graphql.swift index cce2940060..0ad21168ab 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatSessionsInput.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/QueryChatSessionsInput.graphql.swift @@ -11,10 +11,18 @@ public struct QueryChatSessionsInput: InputObject { } public init( - action: GraphQLNullable = nil + action: GraphQLNullable = nil, + fork: GraphQLNullable = nil, + limit: GraphQLNullable = nil, + pinned: GraphQLNullable = nil, + skip: GraphQLNullable = nil ) { __data = InputDict([ - "action": action + "action": action, + "fork": fork, + "limit": limit, + "pinned": pinned, + "skip": skip ]) } @@ -22,4 +30,24 @@ public struct QueryChatSessionsInput: InputObject { get { __data["action"] } set { __data["action"] = newValue } } + + public var fork: GraphQLNullable { + get { __data["fork"] } + set { __data["fork"] = newValue } + } + + public var limit: GraphQLNullable { + get { __data["limit"] } + set { __data["limit"] = newValue } + } + + public var pinned: GraphQLNullable { + get { __data["pinned"] } + set { __data["pinned"] = newValue } + } + + public var skip: GraphQLNullable { + get { __data["skip"] } + set { __data["skip"] = newValue } + } } diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/SearchDocsInput.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/SearchDocsInput.graphql.swift new file mode 100644 index 0000000000..ce3a01fd35 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/InputObjects/SearchDocsInput.graphql.swift @@ -0,0 +1,33 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +public struct SearchDocsInput: InputObject { + public private(set) var __data: InputDict + + public init(_ data: InputDict) { + __data = data + } + + public init( + keyword: String, + limit: GraphQLNullable = nil + ) { + __data = InputDict([ + "keyword": keyword, + "limit": limit + ]) + } + + public var keyword: String { + get { __data["keyword"] } + set { __data["keyword"] = newValue } + } + + /// Limit the number of docs to return, default is 20 + public var limit: GraphQLNullable { + get { __data["limit"] } + set { __data["limit"] = newValue } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/SearchDocObjectType.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/SearchDocObjectType.graphql.swift new file mode 100644 index 0000000000..2315b06dcf --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/Objects/SearchDocObjectType.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +public extension Objects { + static let SearchDocObjectType = ApolloAPI.Object( + typename: "SearchDocObjectType", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift index 414967d897..a280d9cf37 100644 --- a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Sources/Schema/SchemaMetadata.graphql.swift @@ -77,6 +77,7 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata { case "Query": return AffineGraphQL.Objects.Query case "ReleaseVersionType": return AffineGraphQL.Objects.ReleaseVersionType case "RemoveAvatar": return AffineGraphQL.Objects.RemoveAvatar + case "SearchDocObjectType": return AffineGraphQL.Objects.SearchDocObjectType case "SearchNodeObjectType": return AffineGraphQL.Objects.SearchNodeObjectType case "SearchResultObjectType": return AffineGraphQL.Objects.SearchResultObjectType case "SearchResultPagination": return AffineGraphQL.Objects.SearchResultPagination diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+GraphQL.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+GraphQL.swift new file mode 100644 index 0000000000..bc5068b326 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+GraphQL.swift @@ -0,0 +1,73 @@ +// +// IntelligentContext+GraphQL.swift +// Intelligents +// +// Created by 秋星桥 on 6/23/25. +// + +import AffineGraphQL +import Apollo +import ApolloAPI +import UIKit + +extension IntelligentContext { + func prepareMetadataFromGraphQlClient(completion: @escaping ([QLMetadataKey: Any]) -> Void) { + var newMetadata: [QLMetadataKey: Any] = [:] + let dispatchGroup = DispatchGroup() + let service = QLService.shared + + dispatchGroup.enter() + service.fetchCurrentUser { user in + if let user { + newMetadata[.userIdentifierKey] = user.id + newMetadata[.userNameKey] = user.name + newMetadata[.userEmailKey] = user.email + if let avatarUrl = user.avatarUrl { + newMetadata[.userAvatarKey] = avatarUrl + } + } + dispatchGroup.leave() + } + + dispatchGroup.enter() + service.fetchUserSettings { settings in + if let settings { + newMetadata[.userSettingsKey] = settings + } + dispatchGroup.leave() + } + + dispatchGroup.enter() + service.fetchWorkspaces { workspaces in + newMetadata[.workspacesCountKey] = workspaces.count + newMetadata[.workspacesKey] = workspaces.map { workspace in + [ + "id": workspace.id, + "team": workspace.team, + ] + } + dispatchGroup.leave() + } + + dispatchGroup.enter() + service.fetchSubscription { subscription in + if let subscription { + newMetadata[.subscriptionStatusKey] = subscription.status + newMetadata[.subscriptionPlanKey] = subscription.plan + } + dispatchGroup.leave() + } + + dispatchGroup.enter() + service.fetchQuota { quota in + if let quota { + newMetadata[.storageQuotaKey] = quota.storageQuota + } + dispatchGroup.leave() + } + + dispatchGroup.notify(queue: .main) { + completion(newMetadata) + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+WebView.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+WebView.swift new file mode 100644 index 0000000000..24f51b2dba --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext+WebView.swift @@ -0,0 +1,33 @@ +// +// IntelligentContext+WebView.swift +// Intelligents +// +// Created by 秋星桥 on 6/23/25. +// + +import UIKit +import WebKit + +extension IntelligentContext { + func prepareMetadataFrom(webView: WKWebView, completion: @escaping ([WebViewMetadataKey: Any]) -> Void) { + var newMetadata: [WebViewMetadataKey: Any] = [:] + let dispatchGroup = DispatchGroup() + let keysAndScripts: [(WebViewMetadataKey, BridgedWindowScript)] = [ + (.currentDocId, .getCurrentDocId), + (.currentWorkspaceId, .getCurrentWorkspaceId), + (.currentServerBaseUrl, .getCurrentServerBaseUrl), + (.currentI18nLocale, .getCurrentI18nLocale), + ] + for (key, script) in keysAndScripts { + DispatchQueue.main.async { + webView.evaluateScript(script) { value in + newMetadata[key] = value // if unable to fetch, clear it + dispatchGroup.leave() + } + } + dispatchGroup.enter() + } + dispatchGroup.wait() + completion(newMetadata) + } +} 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 new file mode 100644 index 0000000000..02b9c80683 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/IntelligentContext.swift @@ -0,0 +1,146 @@ +// +// IntelligentContext.swift +// Intelligents +// +// Created by 秋星桥 on 6/17/25. +// + +import Combine +import Foundation +import WebKit + +public class IntelligentContext { + public static let shared = IntelligentContext() + + public var webView: WKWebView! + + public private(set) var qlMetadata: [QLMetadataKey: Any] = [:] + public enum QLMetadataKey: String, CaseIterable { + case accountIdentifier + case userIdentifierKey + case userNameKey + case userEmailKey + case userAvatarKey + case userSettingsKey + case workspacesCountKey + case workspacesKey + case subscriptionStatusKey + case subscriptionPlanKey + case storageQuotaKey + case storageUsedKey + } + + var isAccountValid: Bool { + true + } + + public private(set) var webViewMetadata: [WebViewMetadataKey: Any] = [:] + public enum WebViewMetadataKey: String, CaseIterable { + case currentDocId + case currentWorkspaceId + case currentServerBaseUrl + case currentI18nLocale + } + + public lazy var temporaryDirectory: URL = { + let tempDir = FileManager.default.temporaryDirectory + return tempDir.appendingPathComponent("IntelligentContext") + }() + + private init() {} + + public func preparePresent(_ completion: @escaping (Result) -> Void) { + DispatchQueue.global(qos: .userInitiated).async { [self] in + prepareTemporaryDirectory() + + let webViewGroup = DispatchGroup() + var webViewMetadataResult: [WebViewMetadataKey: Any] = [:] + webViewGroup.enter() + prepareMetadataFrom(webView: webView) { metadata in + webViewMetadataResult = metadata + webViewGroup.leave() + } + webViewGroup.wait() + webViewMetadata = webViewMetadataResult + + if let baseUrlString = webViewMetadataResult[.currentServerBaseUrl] as? String, + let url = URL(string: baseUrlString) + { + QLService.shared.setEndpoint(base: url) + } + + let gqlGroup = DispatchGroup() + var gqlMetadataResult: [QLMetadataKey: Any] = [:] + gqlGroup.enter() + prepareMetadataFromGraphQlClient { metadata in + gqlMetadataResult = metadata + gqlGroup.leave() + } + gqlGroup.wait() + qlMetadata = gqlMetadataResult + + dumpMetadataContents() + + DispatchQueue.main.async { + completion(.success(())) + } + } + } + + func dumpMetadataContents() { + print("\n========== IntelligentContext Metadata ==========") + print("-- QL Metadata --") + for key in QLMetadataKey.allCases { + let value = qlMetadata[key] ?? "" + print("\(key.rawValue): \(value)") + } + print("\n-- WebView Metadata --") + for key in WebViewMetadataKey.allCases { + let value = webViewMetadata[key] ?? "" + print("\(key.rawValue): \(value)") + } + print("===============================================\n") + } + + func prepareTemporaryDirectory() { + if FileManager.default.fileExists(atPath: temporaryDirectory.path) { + try? FileManager.default.removeItem(at: temporaryDirectory) + } + try? FileManager.default.createDirectory( + at: temporaryDirectory, + withIntermediateDirectories: true + ) + } +} + +/* + + dumpMetadataContents sample: + + ========== IntelligentContext Metadata ========== + -- QL Metadata -- + accountIdentifier: + userIdentifierKey: 82a5a6f0-xxxx-xxxx-xxxx-0de4be320696 + userNameKey: Dev User + userEmailKey: xxx@xxxxx.xxx + userAvatarKey: https://avatar.affineassets.com/82a5a6f0-xxxx-xxxx-xxxx-0de4be320696-avatar-1733191099480 + userSettingsKey: { + "__typename" = UserSettingsType; + receiveInvitationEmail = 1; + receiveMentionEmail = 1; + } + workspacesCountKey: 8 + workspacesKey: [["id": "a0d781bf-xxxx-xxxx-xxxx-19394bad7f24", "team": false], ["id": "b00d1110-xxxx-xxxx-xxxx-4bc39685af7c", "team": false], ["id": "5559196a-xxxx-xxxx-xxxx-fc9ee6e2dbf9", "team": false], ["team": true, "id": "0f58ea6f-xxxx-xxxx-xxxx-30c4b01a346a"], ["id": "c4e72530-xxxx-xxxx-xxxx-888a166c8155", "team": true], ["id": "c924e653-xxxx-xxxx-xxxx-ed4be3a7d7c8", "team": false], ["id": "ac772e5a-xxxx-xxxx-xxxx-4e2049259408", "team": true], ["id": "4dc9c0ca-xxxx-xxxx-xxxx-7b84184f7e1d", "team": true]] + subscriptionStatusKey: case(AffineGraphQL.SubscriptionStatus.active) + subscriptionPlanKey: case(AffineGraphQL.SubscriptionPlan.pro) + storageQuotaKey: 10737418240 + storageUsedKey: + + -- WebView Metadata -- + currentDocId: + currentWorkspaceId: + currentServerBaseUrl: https://affine.fail + currentI18nLocale: en + =============================================== + + */ diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService+URLSessionCookieClient.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService+URLSessionCookieClient.swift new file mode 100644 index 0000000000..483a051d84 --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService+URLSessionCookieClient.swift @@ -0,0 +1,21 @@ +// +// QLService+URLSessionCookieClient.swift +// Intelligents +// +// Created by 秋星桥 on 6/23/25. +// + +import Apollo +import Foundation + +extension QLService { + final class URLSessionCookieClient: URLSessionClient { + public init() { + super.init() + session.configuration.httpCookieStorage = .init() + HTTPCookieStorage.shared.cookies?.forEach { cookie in + self.session.configuration.httpCookieStorage?.setCookie(cookie) + } + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService.swift new file mode 100644 index 0000000000..efc289070c --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/IntelligentContext/QLService.swift @@ -0,0 +1,92 @@ +import AffineGraphQL +import Apollo +import Foundation + +public final class QLService { + public static let shared = QLService() + private var endpointURL: URL + public private(set) var client: ApolloClient + + private init() { + let store = ApolloStore() + endpointURL = URL(string: "https://app.affine.pro/graphql")! + let urlSessionClient = URLSessionCookieClient() + let networkTransport = RequestChainNetworkTransport( + interceptorProvider: DefaultInterceptorProvider(client: urlSessionClient, store: store), + endpointURL: endpointURL + ) + client = ApolloClient(networkTransport: networkTransport, store: store) + } + + public func setEndpoint(base: URL) { + var url: URL = base + if url.lastPathComponent != "graphql" { + url = url.appendingPathComponent("graphql") + } + print("[*] setting endpoint for qlservice: \(url.absoluteString)") + + let store = ApolloStore() + endpointURL = url + let urlSessionClient = URLSessionCookieClient() + let networkTransport = RequestChainNetworkTransport( + interceptorProvider: DefaultInterceptorProvider(client: urlSessionClient, store: store), + endpointURL: url + ) + client = ApolloClient(networkTransport: networkTransport, store: store) + } + + public func fetchCurrentUser(completion: @escaping (GetCurrentUserQuery.Data.CurrentUser?) -> Void) { + client.fetch(query: GetCurrentUserQuery()) { result in + switch result { + case let .success(graphQLResult): + completion(graphQLResult.data?.currentUser) + case .failure: + completion(nil) + } + } + } + + public func fetchUserSettings(completion: @escaping (GetUserSettingsQuery.Data.CurrentUser.Settings?) -> Void) { + client.fetch(query: GetUserSettingsQuery()) { result in + switch result { + case let .success(graphQLResult): + completion(graphQLResult.data?.currentUser?.settings) + case .failure: + completion(nil) + } + } + } + + public func fetchWorkspaces(completion: @escaping ([GetWorkspacesQuery.Data.Workspace]) -> Void) { + client.fetch(query: GetWorkspacesQuery()) { result in + switch result { + case let .success(graphQLResult): + completion(graphQLResult.data?.workspaces ?? []) + case .failure: + completion([]) + } + } + } + + public func fetchSubscription(completion: @escaping (SubscriptionQuery.Data.CurrentUser.Subscription?) -> Void) { + client.fetch(query: SubscriptionQuery()) { result in + switch result { + case let .success(graphQLResult): + completion(graphQLResult.data?.currentUser?.subscriptions.first) + case .failure: + completion(nil) + } + } + } + + public func fetchQuota(completion: @escaping (QuotaQuery.Data.CurrentUser.Quota?) -> Void) { + client.fetch(query: QuotaQuery()) { result in + switch result { + case let .success(graphQLResult): + completion(graphQLResult.data?.currentUser?.quota) + case .failure: + completion(nil) + } + } + } +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift index f07f81b7e0..022c69eb65 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Intelligents.swift @@ -6,15 +6,3 @@ import Apollo import Foundation public enum Intelligents {} - -private extension Intelligents { - private final class URLSessionCookieClient: URLSessionClient { - init() { - super.init() - session.configuration.httpCookieStorage = .init() - HTTPCookieStorage.shared.cookies?.forEach { cookie in - self.session.configuration.httpCookieStorage?.setCookie(cookie) - } - } - } -} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift index fc57bf28c7..db3771a268 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxFunctionBar.swift @@ -13,8 +13,8 @@ protocol InputBoxFunctionBarDelegate: AnyObject { func functionBarDidTapSend(_ functionBar: InputBoxFunctionBar) } -private let unselectedColor: UIColor = UIColor.affineIconPrimary -private let selectedColor: UIColor = UIColor.affineIconActivated +private let unselectedColor: UIColor = .affineIconPrimary +private let selectedColor: UIColor = .affineIconActivated class InputBoxFunctionBar: UIView { weak var delegate: InputBoxFunctionBarDelegate? diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift index 2afaf18fee..8b1cdf3577 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Interface/View/InputBox/InputBoxImageBar.swift @@ -62,7 +62,7 @@ class InputBoxImageBar: UIScrollView { // 添加新的附件 let idsToAdd = newIds.subtracting(currentIds) var initialXOffset = attachmentViewModels.reduce(0) { $0 + $1.imageCell.frame.width + cellSpacing } - for attachment in imageAttachments { + for attachment in imageAttachments { if idsToAdd.contains(attachment.id), let data = attachment.data, let image = UIImage(data: data) diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/BridgedWindowScript.swift b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/BridgedWindowScript.swift index 8701e06dba..9009e8eb24 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/BridgedWindowScript.swift +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/BridgedWindowScript.swift @@ -1,5 +1,5 @@ // -// ApplicationBridgedWindowScript.swift +// BridgedWindowScript.swift // App // // Created by 秋星桥 on 2025/1/8. @@ -22,14 +22,14 @@ enum BridgedWindowScript: String { var requiresAsyncContext: Bool { switch self { - case .getCurrentDocContentInMarkdown, .createNewDocByMarkdownInCurrentWorkspace: return true - default: return false + case .getCurrentDocContentInMarkdown, .createNewDocByMarkdownInCurrentWorkspace: true + default: false } } } extension WKWebView { - func evaluateScript(_ script: BridgedWindowScript, callback: @escaping (Any?) -> ()) { + func evaluateScript(_ script: BridgedWindowScript, callback: @escaping (Any?) -> Void) { if script.requiresAsyncContext { callAsyncJavaScript( script.rawValue, @@ -38,7 +38,7 @@ extension WKWebView { in: .page ) { result in switch result { - case .success(let input): + case let .success(input): callback(input) case .failure: callback(nil) @@ -49,5 +49,3 @@ extension WKWebView { } } } - - 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 deleted file mode 100644 index 3a1f3132e1..0000000000 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Sources/Intelligents/Model/IntelligentContext.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// IntelligentContext.swift -// Intelligents -// -// Created by 秋星桥 on 6/17/25. -// - -import Combine -import Foundation -import WebKit - -public class IntelligentContext { - // shared across the app, we expect our app to have a single context and webview - public static let shared = IntelligentContext() - - public var webView: WKWebView! - - public private(set) var metadata: [MetadataKey: Any] = [:] - public enum MetadataKey: String { - case currentDocId - case currentWorkspaceId - case currentServerBaseUrl - case currentI18nLocale - } - - public lazy var temporaryDirectory: URL = { - let tempDir = FileManager.default.temporaryDirectory - return tempDir.appendingPathComponent("IntelligentContext") - }() - - private init() {} - - public func preparePresent(_ completion: @escaping () -> Void) { - DispatchQueue.global(qos: .userInitiated).async { [self] in - prepareTemporaryDirectory() - - let group = DispatchGroup() - var newMetadata: [MetadataKey: Any] = [:] - let keysAndScripts: [(MetadataKey, BridgedWindowScript)] = [ - (.currentDocId, .getCurrentDocId), - (.currentWorkspaceId, .getCurrentWorkspaceId), - (.currentServerBaseUrl, .getCurrentServerBaseUrl), - (.currentI18nLocale, .getCurrentI18nLocale) - ] - for (key, script) in keysAndScripts { - DispatchQueue.main.async { - self.webView.evaluateScript(script) { value in - newMetadata[key] = value // if unable to fetch, clear it - group.leave() - } - } - group.enter() - } - self.metadata = newMetadata - group.wait() - print("IntelligentContext metadata prepared: \(self.metadata)") - DispatchQueue.main.async { - completion() - } - } - } - - func prepareTemporaryDirectory() { - if FileManager.default.fileExists(atPath: temporaryDirectory.path) { - try? FileManager.default.removeItem(at: temporaryDirectory) - } - try? FileManager.default.createDirectory( - at: temporaryDirectory, - withIntermediateDirectories: true - ) - } -}