chore: update graphql support for fetching initial user status (#12905)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new context management system for intelligent features, enabling the collection and preparation of metadata from both web view and GraphQL sources. - Added a service for managing GraphQL API interactions, including user, workspace, subscription, and quota queries. - Enabled searching documents within a workspace using a new GraphQL query and input structure. - **Enhancements** - Expanded chat session and chat history search capabilities with additional filter and pagination options. - **Refactor** - Replaced the previous context management class with a more comprehensive and modular implementation. - Improved handling of cookies for network requests to ensure session continuity. - **Style** - Minor code style and formatting improvements for clarity and consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
63de20c3d5
commit
10139205b4
@ -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)
|
||||
|
@ -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"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ public struct QueryChatHistoriesInput: InputObject {
|
||||
fork: GraphQLNullable<Bool> = nil,
|
||||
limit: GraphQLNullable<Int> = nil,
|
||||
messageOrder: GraphQLNullable<GraphQLEnum<ChatHistoryOrder>> = nil,
|
||||
pinned: GraphQLNullable<Bool> = nil,
|
||||
sessionId: GraphQLNullable<String> = nil,
|
||||
sessionOrder: GraphQLNullable<GraphQLEnum<ChatHistoryOrder>> = nil,
|
||||
skip: GraphQLNullable<Int> = 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<Bool> {
|
||||
get { __data["pinned"] }
|
||||
set { __data["pinned"] = newValue }
|
||||
}
|
||||
|
||||
public var sessionId: GraphQLNullable<String> {
|
||||
get { __data["sessionId"] }
|
||||
set { __data["sessionId"] = newValue }
|
||||
|
@ -11,10 +11,18 @@ public struct QueryChatSessionsInput: InputObject {
|
||||
}
|
||||
|
||||
public init(
|
||||
action: GraphQLNullable<Bool> = nil
|
||||
action: GraphQLNullable<Bool> = nil,
|
||||
fork: GraphQLNullable<Bool> = nil,
|
||||
limit: GraphQLNullable<Int> = nil,
|
||||
pinned: GraphQLNullable<Bool> = nil,
|
||||
skip: GraphQLNullable<Int> = 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<Bool> {
|
||||
get { __data["fork"] }
|
||||
set { __data["fork"] = newValue }
|
||||
}
|
||||
|
||||
public var limit: GraphQLNullable<Int> {
|
||||
get { __data["limit"] }
|
||||
set { __data["limit"] = newValue }
|
||||
}
|
||||
|
||||
public var pinned: GraphQLNullable<Bool> {
|
||||
get { __data["pinned"] }
|
||||
set { __data["pinned"] = newValue }
|
||||
}
|
||||
|
||||
public var skip: GraphQLNullable<Int> {
|
||||
get { __data["skip"] }
|
||||
set { __data["skip"] = newValue }
|
||||
}
|
||||
}
|
||||
|
@ -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<Int> = 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<Int> {
|
||||
get { __data["limit"] }
|
||||
set { __data["limit"] = newValue }
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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, Error>) -> 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] ?? "<nil>"
|
||||
print("\(key.rawValue): \(value)")
|
||||
}
|
||||
print("\n-- WebView Metadata --")
|
||||
for key in WebViewMetadataKey.allCases {
|
||||
let value = webViewMetadata[key] ?? "<nil>"
|
||||
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: <nil>
|
||||
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: <nil>
|
||||
|
||||
-- WebView Metadata --
|
||||
currentDocId: <null>
|
||||
currentWorkspaceId: <null>
|
||||
currentServerBaseUrl: https://affine.fail
|
||||
currentI18nLocale: en
|
||||
===============================================
|
||||
|
||||
*/
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user