feat(ios): translate & continue to chat & clear history (#11347)

This commit is contained in:
Lakr
2025-04-01 18:03:48 +08:00
committed by GitHub
parent b74d40d45f
commit 032244ae61
43 changed files with 785 additions and 426 deletions

View File

@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 77; objectVersion = 56;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */

View File

@@ -5,8 +5,17 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apollographql/apollo-ios", "location" : "https://github.com/apollographql/apollo-ios",
"state" : { "state" : {
"revision" : "9aa748d6f0526a744d49d59a2383dc7fdf9d645b", "revision" : "4d0845f9f2901ed657d680c874ffc68d12704cd4",
"version" : "1.18.0" "version" : "1.19.0"
}
},
{
"identity" : "chidorimenu",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Lakr233/ChidoriMenu",
"state" : {
"revision" : "ccd37f01cd4dcf4fb0889f263573d90d5c16e59b",
"version" : "2.4.3"
} }
}, },
{ {
@@ -27,6 +36,15 @@
"version" : "0.15.3" "version" : "0.15.3"
} }
}, },
{
"identity" : "swift-cmark",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-cmark",
"state" : {
"revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
"version" : "0.5.0"
}
},
{ {
"identity" : "swift-collections", "identity" : "swift-collections",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -1,13 +1,13 @@
// //
// File.swift // AffineViewController+AIButton.swift
// App // App
// //
// Created by 秋星桥 on 2025/1/8. // Created by 秋星桥 on 2025/1/8.
// //
import UIKit
import Intelligents
import ChidoriMenu import ChidoriMenu
import Intelligents
import UIKit
extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate { extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApertureViewDelegate {
func onIntelligentsButtonTapped(_ button: IntelligentsButton) { func onIntelligentsButtonTapped(_ button: IntelligentsButton) {
@@ -102,7 +102,7 @@ extension AFFiNEViewController: IntelligentsButtonDelegate, IntelligentsFocusApe
presentIntoCurrentContext(withTargetController: controller) presentIntoCurrentContext(withTargetController: controller)
}) })
} }
view.present(menu: .init(children: actions)) { controller in view.present(menu: .init(children: actions)) { controller in
controller.overrideUserInterfaceStyle = .dark controller.overrideUserInterfaceStyle = .dark
} controllerDidPresent: { _ in } } controllerDidPresent: { _ in }
case .summary: case .summary:

View File

@@ -14,7 +14,7 @@ let package = Package(
.library(name: "AffineGraphQL", targets: ["AffineGraphQL"]), .library(name: "AffineGraphQL", targets: ["AffineGraphQL"]),
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/apollographql/apollo-ios", exact: "1.19.0"), .package(url: "https://github.com/apollographql/apollo-ios", exact: "1.18.0"),
], ],
targets: [ targets: [
.target( .target(

View File

@@ -7,7 +7,7 @@ public class ClaimAudioTranscriptionMutation: GraphQLMutation {
public static let operationName: String = "claimAudioTranscription" public static let operationName: String = "claimAudioTranscription"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"mutation claimAudioTranscription($jobId: String!) { claimAudioTranscription(jobId: $jobId) { __typename id status transcription { __typename speaker start end transcription } summary } }"# #"mutation claimAudioTranscription($jobId: String!) { claimAudioTranscription(jobId: $jobId) { __typename id status title summary transcription { __typename speaker start end transcription } } }"#
)) ))
public var jobId: String public var jobId: String
@@ -41,14 +41,16 @@ public class ClaimAudioTranscriptionMutation: GraphQLMutation {
.field("__typename", String.self), .field("__typename", String.self),
.field("id", AffineGraphQL.ID.self), .field("id", AffineGraphQL.ID.self),
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self), .field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
.field("transcription", [Transcription]?.self), .field("title", String?.self),
.field("summary", String?.self), .field("summary", String?.self),
.field("transcription", [Transcription]?.self),
] } ] }
public var id: AffineGraphQL.ID { __data["id"] } public var id: AffineGraphQL.ID { __data["id"] }
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] } public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
public var transcription: [Transcription]? { __data["transcription"] } public var title: String? { __data["title"] }
public var summary: String? { __data["summary"] } public var summary: String? { __data["summary"] }
public var transcription: [Transcription]? { __data["transcription"] }
/// ClaimAudioTranscription.Transcription /// ClaimAudioTranscription.Transcription
/// ///

View File

@@ -0,0 +1,33 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class UpdateAppConfigMutation: GraphQLMutation {
public static let operationName: String = "updateAppConfig"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation updateAppConfig($updates: [UpdateAppConfigInput!]!) { updateAppConfig(updates: $updates) }"#
))
public var updates: [UpdateAppConfigInput]
public init(updates: [UpdateAppConfigInput]) {
self.updates = updates
}
public var __variables: Variables? { ["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.Mutation }
public static var __selections: [ApolloAPI.Selection] { [
.field("updateAppConfig", AffineGraphQL.JSONObject.self, arguments: ["updates": .variable("updates")]),
] }
/// update app configuration
public var updateAppConfig: AffineGraphQL.JSONObject { __data["updateAppConfig"] }
}
}

View File

@@ -1,51 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class UpdateServerRuntimeConfigsMutation: GraphQLMutation {
public static let operationName: String = "updateServerRuntimeConfigs"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation updateServerRuntimeConfigs($updates: JSONObject!) { updateRuntimeConfigs(updates: $updates) { __typename key value } }"#
))
public var updates: JSONObject
public init(updates: JSONObject) {
self.updates = updates
}
public var __variables: Variables? { ["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.Mutation }
public static var __selections: [ApolloAPI.Selection] { [
.field("updateRuntimeConfigs", [UpdateRuntimeConfig].self, arguments: ["updates": .variable("updates")]),
] }
/// update multiple server runtime configurable settings
public var updateRuntimeConfigs: [UpdateRuntimeConfig] { __data["updateRuntimeConfigs"] }
/// UpdateRuntimeConfig
///
/// Parent Type: `ServerRuntimeConfigType`
public struct UpdateRuntimeConfig: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ServerRuntimeConfigType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("key", String.self),
.field("value", AffineGraphQL.JSON.self),
] }
public var key: String { __data["key"] }
public var value: AffineGraphQL.JSON { __data["value"] }
}
}
}

View File

@@ -7,12 +7,12 @@ public class UpdateUserSettingsMutation: GraphQLMutation {
public static let operationName: String = "updateUserSettings" public static let operationName: String = "updateUserSettings"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"mutation updateUserSettings($input: UpdateSettingsInput!) { updateSettings(input: $input) }"# #"mutation updateUserSettings($input: UpdateUserSettingsInput!) { updateSettings(input: $input) }"#
)) ))
public var input: UpdateSettingsInput public var input: UpdateUserSettingsInput
public init(input: UpdateSettingsInput) { public init(input: UpdateUserSettingsInput) {
self.input = input self.input = input
} }

View File

@@ -42,7 +42,7 @@ public class AdminServerConfigQuery: GraphQLQuery {
.field("type", GraphQLEnum<AffineGraphQL.ServerDeploymentType>.self), .field("type", GraphQLEnum<AffineGraphQL.ServerDeploymentType>.self),
.field("initialized", Bool.self), .field("initialized", Bool.self),
.field("credentialsRequirement", CredentialsRequirement.self), .field("credentialsRequirement", CredentialsRequirement.self),
.field("availableUpgrade", AvailableUpgrade.self), .field("availableUpgrade", AvailableUpgrade?.self),
.field("availableUserFeatures", [GraphQLEnum<AffineGraphQL.FeatureType>].self), .field("availableUserFeatures", [GraphQLEnum<AffineGraphQL.FeatureType>].self),
] } ] }
@@ -61,7 +61,7 @@ public class AdminServerConfigQuery: GraphQLQuery {
/// credentials requirement /// credentials requirement
public var credentialsRequirement: CredentialsRequirement { __data["credentialsRequirement"] } public var credentialsRequirement: CredentialsRequirement { __data["credentialsRequirement"] }
/// fetch latest available upgradable release of server /// fetch latest available upgradable release of server
public var availableUpgrade: AvailableUpgrade { __data["availableUpgrade"] } public var availableUpgrade: AvailableUpgrade? { __data["availableUpgrade"] }
/// Features for user that can be configured /// Features for user that can be configured
public var availableUserFeatures: [GraphQLEnum<AffineGraphQL.FeatureType>] { __data["availableUserFeatures"] } public var availableUserFeatures: [GraphQLEnum<AffineGraphQL.FeatureType>] { __data["availableUserFeatures"] }

View File

@@ -0,0 +1,27 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class AppConfigQuery: GraphQLQuery {
public static let operationName: String = "appConfig"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query appConfig { appConfig }"#
))
public init() {}
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("appConfig", AffineGraphQL.JSONObject.self),
] }
/// get the whole app configuration
public var appConfig: AffineGraphQL.JSONObject { __data["appConfig"] }
}
}

View File

@@ -7,23 +7,27 @@ public class GetAudioTranscriptionQuery: GraphQLQuery {
public static let operationName: String = "getAudioTranscription" public static let operationName: String = "getAudioTranscription"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"query getAudioTranscription($workspaceId: String!, $jobId: String!) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename audioTranscription(jobId: $jobId) { __typename id status transcription { __typename speaker start end transcription } summary } } } }"# #"query getAudioTranscription($workspaceId: String!, $jobId: String, $blobId: String) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename audioTranscription(jobId: $jobId, blobId: $blobId) { __typename id status title summary transcription { __typename speaker start end transcription } } } } }"#
)) ))
public var workspaceId: String public var workspaceId: String
public var jobId: String public var jobId: GraphQLNullable<String>
public var blobId: GraphQLNullable<String>
public init( public init(
workspaceId: String, workspaceId: String,
jobId: String jobId: GraphQLNullable<String>,
blobId: GraphQLNullable<String>
) { ) {
self.workspaceId = workspaceId self.workspaceId = workspaceId
self.jobId = jobId self.jobId = jobId
self.blobId = blobId
} }
public var __variables: Variables? { [ public var __variables: Variables? { [
"workspaceId": workspaceId, "workspaceId": workspaceId,
"jobId": jobId "jobId": jobId,
"blobId": blobId
] } ] }
public struct Data: AffineGraphQL.SelectionSet { public struct Data: AffineGraphQL.SelectionSet {
@@ -63,10 +67,13 @@ public class GetAudioTranscriptionQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot } public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [ public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self), .field("__typename", String.self),
.field("audioTranscription", [AudioTranscription].self, arguments: ["jobId": .variable("jobId")]), .field("audioTranscription", AudioTranscription?.self, arguments: [
"jobId": .variable("jobId"),
"blobId": .variable("blobId")
]),
] } ] }
public var audioTranscription: [AudioTranscription] { __data["audioTranscription"] } public var audioTranscription: AudioTranscription? { __data["audioTranscription"] }
/// CurrentUser.Copilot.AudioTranscription /// CurrentUser.Copilot.AudioTranscription
/// ///
@@ -80,14 +87,16 @@ public class GetAudioTranscriptionQuery: GraphQLQuery {
.field("__typename", String.self), .field("__typename", String.self),
.field("id", AffineGraphQL.ID.self), .field("id", AffineGraphQL.ID.self),
.field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self), .field("status", GraphQLEnum<AffineGraphQL.AiJobStatus>.self),
.field("transcription", [Transcription]?.self), .field("title", String?.self),
.field("summary", String?.self), .field("summary", String?.self),
.field("transcription", [Transcription]?.self),
] } ] }
public var id: AffineGraphQL.ID { __data["id"] } public var id: AffineGraphQL.ID { __data["id"] }
public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] } public var status: GraphQLEnum<AffineGraphQL.AiJobStatus> { __data["status"] }
public var transcription: [Transcription]? { __data["transcription"] } public var title: String? { __data["title"] }
public var summary: String? { __data["summary"] } public var summary: String? { __data["summary"] }
public var transcription: [Transcription]? { __data["transcription"] }
/// CurrentUser.Copilot.AudioTranscription.Transcription /// CurrentUser.Copilot.AudioTranscription.Transcription
/// ///

View File

@@ -7,7 +7,7 @@ public class GetInviteInfoQuery: GraphQLQuery {
public static let operationName: String = "getInviteInfo" public static let operationName: String = "getInviteInfo"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"query getInviteInfo($inviteId: String!) { getInviteInfo(inviteId: $inviteId) { __typename workspace { __typename id name avatar } user { __typename id name avatarUrl } } }"# #"query getInviteInfo($inviteId: String!) { getInviteInfo(inviteId: $inviteId) { __typename workspace { __typename id name avatar } user { __typename id name avatarUrl } status invitee { __typename id name email avatarUrl } } }"#
)) ))
public var inviteId: String public var inviteId: String
@@ -42,12 +42,18 @@ public class GetInviteInfoQuery: GraphQLQuery {
.field("__typename", String.self), .field("__typename", String.self),
.field("workspace", Workspace.self), .field("workspace", Workspace.self),
.field("user", User.self), .field("user", User.self),
.field("status", GraphQLEnum<AffineGraphQL.WorkspaceMemberStatus>?.self),
.field("invitee", Invitee.self),
] } ] }
/// Workspace information /// Workspace information
public var workspace: Workspace { __data["workspace"] } public var workspace: Workspace { __data["workspace"] }
/// User information /// User information
public var user: User { __data["user"] } public var user: User { __data["user"] }
/// Invitation status in workspace
public var status: GraphQLEnum<AffineGraphQL.WorkspaceMemberStatus>? { __data["status"] }
/// Invitee information
public var invitee: Invitee { __data["invitee"] }
/// GetInviteInfo.Workspace /// GetInviteInfo.Workspace
/// ///
@@ -73,23 +79,43 @@ public class GetInviteInfoQuery: GraphQLQuery {
/// GetInviteInfo.User /// GetInviteInfo.User
/// ///
/// Parent Type: `UserType` /// Parent Type: `WorkspaceUserType`
public struct User: AffineGraphQL.SelectionSet { public struct User: AffineGraphQL.SelectionSet {
public let __data: DataDict public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict } public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType } public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceUserType }
public static var __selections: [ApolloAPI.Selection] { [ public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self), .field("__typename", String.self),
.field("id", AffineGraphQL.ID.self), .field("id", String.self),
.field("name", String.self), .field("name", String.self),
.field("avatarUrl", String?.self), .field("avatarUrl", String?.self),
] } ] }
public var id: AffineGraphQL.ID { __data["id"] } public var id: String { __data["id"] }
/// User name
public var name: String { __data["name"] } public var name: String { __data["name"] }
/// User avatar url public var avatarUrl: String? { __data["avatarUrl"] }
}
/// GetInviteInfo.Invitee
///
/// Parent Type: `WorkspaceUserType`
public struct Invitee: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceUserType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", String.self),
.field("name", String.self),
.field("email", String.self),
.field("avatarUrl", String?.self),
] }
public var id: String { __data["id"] }
public var name: String { __data["name"] }
public var email: String { __data["email"] }
public var avatarUrl: String? { __data["avatarUrl"] } public var avatarUrl: String? { __data["avatarUrl"] }
} }
} }

View File

@@ -1,55 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetServerRuntimeConfigQuery: GraphQLQuery {
public static let operationName: String = "getServerRuntimeConfig"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getServerRuntimeConfig { serverRuntimeConfig { __typename id module key description value type updatedAt } }"#
))
public init() {}
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("serverRuntimeConfig", [ServerRuntimeConfig].self),
] }
/// get all server runtime configurable settings
public var serverRuntimeConfig: [ServerRuntimeConfig] { __data["serverRuntimeConfig"] }
/// ServerRuntimeConfig
///
/// Parent Type: `ServerRuntimeConfigType`
public struct ServerRuntimeConfig: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ServerRuntimeConfigType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", String.self),
.field("module", String.self),
.field("key", String.self),
.field("description", String.self),
.field("value", AffineGraphQL.JSON.self),
.field("type", GraphQLEnum<AffineGraphQL.RuntimeConfigType>.self),
.field("updatedAt", AffineGraphQL.DateTime.self),
] }
public var id: String { __data["id"] }
public var module: String { __data["module"] }
public var key: String { __data["key"] }
public var description: String { __data["description"] }
public var value: AffineGraphQL.JSON { __data["value"] }
public var type: GraphQLEnum<AffineGraphQL.RuntimeConfigType> { __data["type"] }
public var updatedAt: AffineGraphQL.DateTime { __data["updatedAt"] }
}
}
}

View File

@@ -1,44 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class GetServerServiceConfigsQuery: GraphQLQuery {
public static let operationName: String = "getServerServiceConfigs"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query getServerServiceConfigs { serverServiceConfigs { __typename name config } }"#
))
public init() {}
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("serverServiceConfigs", [ServerServiceConfig].self),
] }
public var serverServiceConfigs: [ServerServiceConfig] { __data["serverServiceConfigs"] }
/// ServerServiceConfig
///
/// Parent Type: `ServerServiceConfig`
public struct ServerServiceConfig: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ServerServiceConfig }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("name", String.self),
.field("config", AffineGraphQL.JSONObject.self),
] }
public var name: String { __data["name"] }
public var config: AffineGraphQL.JSONObject { __data["config"] }
}
}
}

View File

@@ -7,7 +7,7 @@ public class GetUserByEmailQuery: GraphQLQuery {
public static let operationName: String = "getUserByEmail" public static let operationName: String = "getUserByEmail"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"query getUserByEmail($email: String!) { userByEmail(email: $email) { __typename id name email features hasPassword emailVerified avatarUrl quota { __typename humanReadable { __typename blobLimit historyPeriod memberLimit name storageQuota } } } }"# #"query getUserByEmail($email: String!) { userByEmail(email: $email) { __typename id name email features hasPassword emailVerified avatarUrl disabled } }"#
)) ))
public var email: String public var email: String
@@ -47,7 +47,7 @@ public class GetUserByEmailQuery: GraphQLQuery {
.field("hasPassword", Bool?.self), .field("hasPassword", Bool?.self),
.field("emailVerified", Bool.self), .field("emailVerified", Bool.self),
.field("avatarUrl", String?.self), .field("avatarUrl", String?.self),
.field("quota", Quota.self), .field("disabled", Bool.self),
] } ] }
public var id: AffineGraphQL.ID { __data["id"] } public var id: AffineGraphQL.ID { __data["id"] }
@@ -63,47 +63,8 @@ public class GetUserByEmailQuery: GraphQLQuery {
public var emailVerified: Bool { __data["emailVerified"] } public var emailVerified: Bool { __data["emailVerified"] }
/// User avatar url /// User avatar url
public var avatarUrl: String? { __data["avatarUrl"] } public var avatarUrl: String? { __data["avatarUrl"] }
public var quota: Quota { __data["quota"] } /// User is disabled
public var disabled: Bool { __data["disabled"] }
/// UserByEmail.Quota
///
/// Parent Type: `UserQuotaType`
public struct Quota: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserQuotaType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("humanReadable", HumanReadable.self),
] }
public var humanReadable: HumanReadable { __data["humanReadable"] }
/// UserByEmail.Quota.HumanReadable
///
/// Parent Type: `UserQuotaHumanReadableType`
public struct HumanReadable: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserQuotaHumanReadableType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("blobLimit", String.self),
.field("historyPeriod", String.self),
.field("memberLimit", String.self),
.field("name", String.self),
.field("storageQuota", String.self),
] }
public var blobLimit: String { __data["blobLimit"] }
public var historyPeriod: String { __data["historyPeriod"] }
public var memberLimit: String { __data["memberLimit"] }
public var name: String { __data["name"] }
public var storageQuota: String { __data["storageQuota"] }
}
}
} }
} }
} }

View File

@@ -42,12 +42,12 @@ public class GetUserSettingsQuery: GraphQLQuery {
/// CurrentUser.Settings /// CurrentUser.Settings
/// ///
/// Parent Type: `SettingsType` /// Parent Type: `UserSettingsType`
public struct Settings: AffineGraphQL.SelectionSet { public struct Settings: AffineGraphQL.SelectionSet {
public let __data: DataDict public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict } public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.SettingsType } public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserSettingsType }
public static var __selections: [ApolloAPI.Selection] { [ public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self), .field("__typename", String.self),
.field("receiveInvitationEmail", Bool.self), .field("receiveInvitationEmail", Bool.self),

View File

@@ -7,27 +7,31 @@ public class MatchContextQuery: GraphQLQuery {
public static let operationName: String = "matchContext" public static let operationName: String = "matchContext"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"query matchContext($contextId: String!, $content: String!, $limit: SafeInt) { currentUser { __typename copilot { __typename contexts(contextId: $contextId) { __typename matchContext(content: $content, limit: $limit) { __typename fileId chunk content distance } } } } }"# #"query matchContext($contextId: String!, $content: String!, $limit: SafeInt, $threshold: Float) { currentUser { __typename copilot { __typename contexts(contextId: $contextId) { __typename matchFiles(content: $content, limit: $limit, threshold: $threshold) { __typename fileId chunk content distance } matchWorkspaceDocs(content: $content, limit: $limit, threshold: $threshold) { __typename docId chunk content distance } } } } }"#
)) ))
public var contextId: String public var contextId: String
public var content: String public var content: String
public var limit: GraphQLNullable<SafeInt> public var limit: GraphQLNullable<SafeInt>
public var threshold: GraphQLNullable<Double>
public init( public init(
contextId: String, contextId: String,
content: String, content: String,
limit: GraphQLNullable<SafeInt> limit: GraphQLNullable<SafeInt>,
threshold: GraphQLNullable<Double>
) { ) {
self.contextId = contextId self.contextId = contextId
self.content = content self.content = content
self.limit = limit self.limit = limit
self.threshold = threshold
} }
public var __variables: Variables? { [ public var __variables: Variables? { [
"contextId": contextId, "contextId": contextId,
"content": content, "content": content,
"limit": limit "limit": limit,
"threshold": threshold
] } ] }
public struct Data: AffineGraphQL.SelectionSet { public struct Data: AffineGraphQL.SelectionSet {
@@ -83,19 +87,27 @@ public class MatchContextQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotContext } public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotContext }
public static var __selections: [ApolloAPI.Selection] { [ public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self), .field("__typename", String.self),
.field("matchContext", [MatchContext].self, arguments: [ .field("matchFiles", [MatchFile].self, arguments: [
"content": .variable("content"), "content": .variable("content"),
"limit": .variable("limit") "limit": .variable("limit"),
"threshold": .variable("threshold")
]),
.field("matchWorkspaceDocs", [MatchWorkspaceDoc].self, arguments: [
"content": .variable("content"),
"limit": .variable("limit"),
"threshold": .variable("threshold")
]), ]),
] } ] }
/// match file context /// match file in context
public var matchContext: [MatchContext] { __data["matchContext"] } public var matchFiles: [MatchFile] { __data["matchFiles"] }
/// match workspace docs
public var matchWorkspaceDocs: [MatchWorkspaceDoc] { __data["matchWorkspaceDocs"] }
/// CurrentUser.Copilot.Context.MatchContext /// CurrentUser.Copilot.Context.MatchFile
/// ///
/// Parent Type: `ContextMatchedFileChunk` /// Parent Type: `ContextMatchedFileChunk`
public struct MatchContext: AffineGraphQL.SelectionSet { public struct MatchFile: AffineGraphQL.SelectionSet {
public let __data: DataDict public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict } public init(_dataDict: DataDict) { __data = _dataDict }
@@ -113,6 +125,28 @@ public class MatchContextQuery: GraphQLQuery {
public var content: String { __data["content"] } public var content: String { __data["content"] }
public var distance: Double? { __data["distance"] } public var distance: Double? { __data["distance"] }
} }
/// CurrentUser.Copilot.Context.MatchWorkspaceDoc
///
/// Parent Type: `ContextMatchedDocChunk`
public struct MatchWorkspaceDoc: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ContextMatchedDocChunk }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("docId", String.self),
.field("chunk", AffineGraphQL.SafeInt.self),
.field("content", String.self),
.field("distance", Double?.self),
] }
public var docId: String { __data["docId"] }
public var chunk: AffineGraphQL.SafeInt { __data["chunk"] }
public var content: String { __data["content"] }
public var distance: Double? { __data["distance"] }
}
} }
} }
} }

View File

@@ -0,0 +1,120 @@
// @generated
// This file was automatically generated and should not be edited.
@_exported import ApolloAPI
public class MatchFilesQuery: GraphQLQuery {
public static let operationName: String = "matchFiles"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"query matchFiles($contextId: String!, $content: String!, $limit: SafeInt) { currentUser { __typename copilot { __typename contexts(contextId: $contextId) { __typename matchFiles(content: $content, limit: $limit) { __typename fileId chunk content distance } } } } }"#
))
public var contextId: String
public var content: String
public var limit: GraphQLNullable<SafeInt>
public init(
contextId: String,
content: String,
limit: GraphQLNullable<SafeInt>
) {
self.contextId = contextId
self.content = content
self.limit = limit
}
public var __variables: Variables? { [
"contextId": contextId,
"content": content,
"limit": limit
] }
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("currentUser", CurrentUser?.self),
] }
/// Get current user
public var currentUser: CurrentUser? { __data["currentUser"] }
/// CurrentUser
///
/// Parent Type: `UserType`
public struct CurrentUser: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("copilot", Copilot.self),
] }
public var copilot: Copilot { __data["copilot"] }
/// CurrentUser.Copilot
///
/// Parent Type: `Copilot`
public struct Copilot: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("contexts", [Context].self, arguments: ["contextId": .variable("contextId")]),
] }
/// Get the context list of a session
public var contexts: [Context] { __data["contexts"] }
/// CurrentUser.Copilot.Context
///
/// Parent Type: `CopilotContext`
public struct Context: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotContext }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("matchFiles", [MatchFile].self, arguments: [
"content": .variable("content"),
"limit": .variable("limit")
]),
] }
/// match file in context
public var matchFiles: [MatchFile] { __data["matchFiles"] }
/// CurrentUser.Copilot.Context.MatchFile
///
/// Parent Type: `ContextMatchedFileChunk`
public struct MatchFile: AffineGraphQL.SelectionSet {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ContextMatchedFileChunk }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("fileId", String.self),
.field("chunk", AffineGraphQL.SafeInt.self),
.field("content", String.self),
.field("distance", Double?.self),
] }
public var fileId: String { __data["fileId"] }
public var chunk: AffineGraphQL.SafeInt { __data["chunk"] }
public var content: String { __data["content"] }
public var distance: Double? { __data["distance"] }
}
}
}
}
}
}

View File

@@ -3,11 +3,11 @@
@_exported import ApolloAPI @_exported import ApolloAPI
public class MatchWorkspaceContextQuery: GraphQLQuery { public class MatchWorkspaceDocsQuery: GraphQLQuery {
public static let operationName: String = "matchWorkspaceContext" public static let operationName: String = "matchWorkspaceDocs"
public static let operationDocument: ApolloAPI.OperationDocument = .init( public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init( definition: .init(
#"query matchWorkspaceContext($contextId: String!, $content: String!, $limit: SafeInt) { currentUser { __typename copilot { __typename contexts(contextId: $contextId) { __typename matchWorkspaceContext(content: $content, limit: $limit) { __typename docId chunk content distance } } } } }"# #"query matchWorkspaceDocs($contextId: String!, $content: String!, $limit: SafeInt) { currentUser { __typename copilot { __typename contexts(contextId: $contextId) { __typename matchWorkspaceDocs(content: $content, limit: $limit) { __typename docId chunk content distance } } } } }"#
)) ))
public var contextId: String public var contextId: String
@@ -83,19 +83,19 @@ public class MatchWorkspaceContextQuery: GraphQLQuery {
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotContext } public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotContext }
public static var __selections: [ApolloAPI.Selection] { [ public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self), .field("__typename", String.self),
.field("matchWorkspaceContext", MatchWorkspaceContext.self, arguments: [ .field("matchWorkspaceDocs", [MatchWorkspaceDoc].self, arguments: [
"content": .variable("content"), "content": .variable("content"),
"limit": .variable("limit") "limit": .variable("limit")
]), ]),
] } ] }
/// match workspace doc content /// match workspace docs
public var matchWorkspaceContext: MatchWorkspaceContext { __data["matchWorkspaceContext"] } public var matchWorkspaceDocs: [MatchWorkspaceDoc] { __data["matchWorkspaceDocs"] }
/// CurrentUser.Copilot.Context.MatchWorkspaceContext /// CurrentUser.Copilot.Context.MatchWorkspaceDoc
/// ///
/// Parent Type: `ContextMatchedDocChunk` /// Parent Type: `ContextMatchedDocChunk`
public struct MatchWorkspaceContext: AffineGraphQL.SelectionSet { public struct MatchWorkspaceDoc: AffineGraphQL.SelectionSet {
public let __data: DataDict public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict } public init(_dataDict: DataDict) { __data = _dataDict }

View File

@@ -7,5 +7,4 @@
import ApolloAPI import ApolloAPI
/// The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). public typealias JSON = JSONObject
public typealias JSON = String

View File

@@ -7,5 +7,36 @@
import ApolloAPI import ApolloAPI
/// The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). public typealias JSONObject = CustomJSON
public typealias JSONObject = String
public enum CustomJSON: CustomScalarType, Hashable {
case dictionary([String: AnyHashable])
case array([AnyHashable])
public init(_jsonValue value: JSONValue) throws {
if let dict = value as? [String: AnyHashable] {
self = .dictionary(dict)
} else if let array = value as? [AnyHashable] {
self = .array(array)
} else {
throw JSONDecodingError.couldNotConvert(value: value, to: CustomJSON.self)
}
}
public var _jsonValue: JSONValue {
switch self {
case let .dictionary(json as AnyHashable),
let .array(json as AnyHashable):
json
}
}
public static func == (lhs: CustomJSON, rhs: CustomJSON) -> Bool {
lhs._jsonValue == rhs._jsonValue
}
public func hash(into hasher: inout Hasher) {
hasher.combine(_jsonValue)
}
}

View File

@@ -1,12 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public enum RuntimeConfigType: String, EnumType {
case array = "Array"
case boolean = "Boolean"
case number = "Number"
case object = "Object"
case string = "String"
}

View File

@@ -0,0 +1,39 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public struct UpdateAppConfigInput: InputObject {
public private(set) var __data: InputDict
public init(_ data: InputDict) {
__data = data
}
public init(
key: String,
module: String,
value: JSON
) {
__data = InputDict([
"key": key,
"module": module,
"value": value
])
}
public var key: String {
get { __data["key"] }
set { __data["key"] = newValue }
}
public var module: String {
get { __data["module"] }
set { __data["module"] = newValue }
}
public var value: JSON {
get { __data["value"] }
set { __data["value"] = newValue }
}
}

View File

@@ -3,7 +3,7 @@
import ApolloAPI import ApolloAPI
public struct UpdateSettingsInput: InputObject { public struct UpdateUserSettingsInput: InputObject {
public private(set) var __data: InputDict public private(set) var __data: InputDict
public init(_ data: InputDict) { public init(_ data: InputDict) {

View File

@@ -1,12 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let ServerRuntimeConfigType = ApolloAPI.Object(
typename: "ServerRuntimeConfigType",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -1,12 +0,0 @@
// @generated
// This file was automatically generated and should not be edited.
import ApolloAPI
public extension Objects {
static let ServerServiceConfig = ApolloAPI.Object(
typename: "ServerServiceConfig",
implementedInterfaces: [],
keyFields: nil
)
}

View File

@@ -4,8 +4,8 @@
import ApolloAPI import ApolloAPI
public extension Objects { public extension Objects {
static let SettingsType = ApolloAPI.Object( static let UserSettingsType = ApolloAPI.Object(
typename: "SettingsType", typename: "UserSettingsType",
implementedInterfaces: [], implementedInterfaces: [],
keyFields: nil keyFields: nil
) )

View File

@@ -65,9 +65,6 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "ReleaseVersionType": return AffineGraphQL.Objects.ReleaseVersionType case "ReleaseVersionType": return AffineGraphQL.Objects.ReleaseVersionType
case "RemoveAvatar": return AffineGraphQL.Objects.RemoveAvatar case "RemoveAvatar": return AffineGraphQL.Objects.RemoveAvatar
case "ServerConfigType": return AffineGraphQL.Objects.ServerConfigType case "ServerConfigType": return AffineGraphQL.Objects.ServerConfigType
case "ServerRuntimeConfigType": return AffineGraphQL.Objects.ServerRuntimeConfigType
case "ServerServiceConfig": return AffineGraphQL.Objects.ServerServiceConfig
case "SettingsType": return AffineGraphQL.Objects.SettingsType
case "SubscriptionPrice": return AffineGraphQL.Objects.SubscriptionPrice case "SubscriptionPrice": return AffineGraphQL.Objects.SubscriptionPrice
case "SubscriptionType": return AffineGraphQL.Objects.SubscriptionType case "SubscriptionType": return AffineGraphQL.Objects.SubscriptionType
case "TranscriptionItemType": return AffineGraphQL.Objects.TranscriptionItemType case "TranscriptionItemType": return AffineGraphQL.Objects.TranscriptionItemType
@@ -76,6 +73,7 @@ public enum SchemaMetadata: ApolloAPI.SchemaMetadata {
case "UserQuotaHumanReadableType": return AffineGraphQL.Objects.UserQuotaHumanReadableType case "UserQuotaHumanReadableType": return AffineGraphQL.Objects.UserQuotaHumanReadableType
case "UserQuotaType": return AffineGraphQL.Objects.UserQuotaType case "UserQuotaType": return AffineGraphQL.Objects.UserQuotaType
case "UserQuotaUsageType": return AffineGraphQL.Objects.UserQuotaUsageType case "UserQuotaUsageType": return AffineGraphQL.Objects.UserQuotaUsageType
case "UserSettingsType": return AffineGraphQL.Objects.UserSettingsType
case "UserType": return AffineGraphQL.Objects.UserType case "UserType": return AffineGraphQL.Objects.UserType
case "WorkspacePageMeta": return AffineGraphQL.Objects.WorkspacePageMeta case "WorkspacePageMeta": return AffineGraphQL.Objects.WorkspacePageMeta
case "WorkspacePermissions": return AffineGraphQL.Objects.WorkspacePermissions case "WorkspacePermissions": return AffineGraphQL.Objects.WorkspacePermissions

View File

@@ -16,7 +16,7 @@ let package = Package(
dependencies: [ dependencies: [
.package(path: "../AffineGraphQL"), .package(path: "../AffineGraphQL"),
.package(path: "../MarkdownView"), .package(path: "../MarkdownView"),
.package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.19.0"), .package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.18.0"),
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", from: "3.3.0"), .package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", from: "3.3.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),
.package(url: "https://github.com/Lakr233/ChidoriMenu", from: "2.4.3"), .package(url: "https://github.com/Lakr233/ChidoriMenu", from: "2.4.3"),

View File

@@ -0,0 +1,45 @@
//
// UnableTo.swift
// Intelligents
//
// Created by 秋星桥 on 4/1/25.
//
import Foundation
private let domain = "Intelligents"
enum UnableTo {
static let identifyDocumentOrWorkspace =
NSError(
domain: domain,
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Unable to identify the document or workspace"]
)
static let createSession = NSError(
domain: domain,
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Unable to create a session"]
)
static let createMessage = NSError(
domain: domain,
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Unable to create a message"]
)
static let compressImage = NSError(
domain: domain,
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "Failed to compress image data",
]
)
static let clearHistory = NSError(
domain: domain,
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Unable to clear history"]
)
}

View File

@@ -25,9 +25,7 @@ extension InputEditView: UIImagePickerControllerDelegate, UINavigationController
private func processJPEGImageData(_ image: UIImage) throws -> Data? { private func processJPEGImageData(_ image: UIImage) throws -> Data? {
guard let data = image.jpegData(compressionQuality: 0.75) else { guard let data = image.jpegData(compressionQuality: 0.75) else {
throw NSError(domain: "", code: -1, userInfo: [ throw UnableTo.compressImage
NSLocalizedDescriptionKey: "Failed to compress image data",
])
} }
return data return data
} }

View File

@@ -8,7 +8,7 @@
import Combine import Combine
import UIKit import UIKit
class InputEditView: UIView, UITextViewDelegate { class InputEditView: UIView {
let mainStack = UIStackView() let mainStack = UIStackView()
let attachmentsEditor = AttachmentBannerView() let attachmentsEditor = AttachmentBannerView()
let textEditor = PlainTextEditView() let textEditor = PlainTextEditView()
@@ -22,6 +22,8 @@ class InputEditView: UIView, UITextViewDelegate {
} }
} }
var submitAction: (() -> Void) = {}
init() { init() {
super.init(frame: .zero) super.init(frame: .zero)
@@ -38,7 +40,6 @@ class InputEditView: UIView, UITextViewDelegate {
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor), mainStack.bottomAnchor.constraint(equalTo: bottomAnchor),
].forEach { $0.isActive = true } ].forEach { $0.isActive = true }
textEditor.delegate = self
textEditor.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true textEditor.heightAnchor.constraint(greaterThanOrEqualToConstant: 64).isActive = true
[ [
@@ -89,6 +90,15 @@ class InputEditView: UIView, UITextViewDelegate {
.store(in: &viewModel.cancellables) .store(in: &viewModel.cancellables)
updateValues() updateValues()
textEditor.textDidChange = { [weak self] text in
self?.viewModel.text = text
self?.updatePlaceholderVisibility()
}
textEditor.textDidReturn = { [weak self] in
self?.submitAction()
}
} }
@available(*, unavailable) @available(*, unavailable)
@@ -96,18 +106,6 @@ class InputEditView: UIView, UITextViewDelegate {
fatalError() fatalError()
} }
func textViewDidChange(_ textView: UITextView) {
viewModel.text = textView.text
}
func textViewDidBeginEditing(_: UITextView) {
updatePlaceholderVisibility()
}
func textViewDidEndEditing(_: UITextView) {
updatePlaceholderVisibility()
}
func updatePlaceholderVisibility() { func updatePlaceholderVisibility() {
let visible = viewModel.text.isEmpty && !textEditor.isFirstResponder let visible = viewModel.text.isEmpty && !textEditor.isFirstResponder
UIView.animate(withDuration: 0.25) { UIView.animate(withDuration: 0.25) {

View File

@@ -8,6 +8,9 @@
import UIKit import UIKit
class PlainTextEditView: UITextView, UITextViewDelegate { class PlainTextEditView: UITextView, UITextViewDelegate {
var textDidChange: ((String) -> Void) = { _ in }
var textDidReturn: (() -> Void) = {}
init() { init() {
super.init(frame: .zero, textContainer: nil) super.init(frame: .zero, textContainer: nil)
@@ -34,4 +37,33 @@ class PlainTextEditView: UITextView, UITextViewDelegate {
required init?(coder _: NSCoder) { required init?(coder _: NSCoder) {
fatalError() fatalError()
} }
func textViewDidChange(_ textView: UITextView) {
textDidChange(textView.text)
}
func textViewDidBeginEditing(_ textView: UITextView) {
textDidChange(textView.text)
}
func textViewDidEndEditing(_ textView: UITextView) {
textDidChange(textView.text)
}
func textView(_: UITextView, editMenuForTextIn _: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {
.init(children: suggestedActions + [
UIAction(title: "Insert Newline") { [weak self] _ in
self?.insertText("\n")
},
])
}
func textView(_: UITextView, shouldChangeTextIn _: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
textDidReturn()
return false
} else {
return true
}
}
} }

View File

@@ -15,7 +15,11 @@ extension IntelligentsChatController {
beginProgress() beginProgress()
chat_createSession { session in chat_createSession { session in
self.sessionID = session ?? "" self.sessionID = session ?? ""
self.endProgress() self.chat_retrieveHistories {
self.dispatchToMain {
self.endProgress()
}
}
} onFailure: { error in } onFailure: { error in
self.presentError(error) { self.presentError(error) {
if let nav = self.navigationController { if let nav = self.navigationController {
@@ -38,6 +42,85 @@ extension IntelligentsChatController {
self.endProgress() self.endProgress()
} }
} }
func chat_clearHistory() {
beginProgress()
Intelligents.qlClient.perform(mutation: CleanupCopilotSessionMutation(input: .init(
docId: metadata[.documentID] ?? "",
sessionIds: [sessionID],
workspaceId: metadata[.workspaceID] ?? ""
))) { result in
self.dispatchToMain {
self.endProgress()
if case let .success(value) = result,
let sessions = value.data?.cleanupCopilotSession,
sessions.contains(self.sessionID)
{
self.simpleChatContents.removeAll()
return
}
self.presentError(UnableTo.clearHistory)
}
}
}
func chat_retrieveHistories(_ completion: @escaping () -> Void) {
Intelligents.qlClient.fetch(query: GetCopilotHistoriesQuery(
workspaceId: metadata[.workspaceID] ?? "",
docId: .init(stringLiteral: metadata[.documentID] ?? ""),
options: .some(.init(
action: false,
fork: false,
limit: .init(nilLiteral: ()),
messageOrder: .some(.case(.asc)),
sessionId: .init(stringLiteral: sessionID),
sessionOrder: .some(.case(.desc)),
skip: .init(nilLiteral: ()),
withPrompt: .init(booleanLiteral: false)
))
)) { [weak self] result in
if let self,
case let .success(value) = result,
let object = value.data,
let currentUser = object.__data._data["currentUser"] as? DataDict,
let copilot = currentUser._data["copilot"] as? DataDict,
let histories = copilot._data["histories"] as? [DataDict],
let mostRecent = histories.first,
let messages = mostRecent._data["messages"] as? [DataDict],
!messages.isEmpty
{
print("[*] retrieved \(messages.count) messages")
tableView.scrollToBottomOnNextUpdate = true
tableView.alpha = 0
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) {
self.tableView.alpha = 1
}
}
for message in messages {
guard let role = message._data["role"] as? String,
let content = message._data["content"] as? String
// TODO: ATTACHMENTS
else { continue }
switch role {
case "assistant":
simpleChatContents.updateValue(
.assistant(document: content),
forKey: UUID()
)
case "user":
simpleChatContents.updateValue(
.user(document: content),
forKey: UUID()
)
default:
assertionFailure()
}
}
}
completion()
}
}
} }
private extension IntelligentsChatController { private extension IntelligentsChatController {
@@ -51,6 +134,7 @@ private extension IntelligentsChatController {
func beginProgress() { func beginProgress() {
dispatchToMain { [self] in dispatchToMain { [self] in
header.isUserInteractionEnabled = false
inputBox.isUserInteractionEnabled = false inputBox.isUserInteractionEnabled = false
progressView.isHidden = false progressView.isHidden = false
progressView.alpha = 0 progressView.alpha = 0
@@ -67,6 +151,7 @@ private extension IntelligentsChatController {
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
self.inputBox.editor.alpha = 1 self.inputBox.editor.alpha = 1
self.progressView.alpha = 0 self.progressView.alpha = 0
self.header.isUserInteractionEnabled = true
} completion: { _ in } completion: { _ in
self.inputBox.isUserInteractionEnabled = true self.inputBox.isUserInteractionEnabled = true
self.progressView.stopAnimating() self.progressView.stopAnimating()
@@ -86,9 +171,44 @@ private extension IntelligentsChatController {
} }
func chat_createSession( func chat_createSession(
forceCreateNewSession: Bool = false,
onSuccess: @escaping (String?) -> Void, onSuccess: @escaping (String?) -> Void,
onFailure: @escaping (Error) -> Void onFailure: @escaping (Error) -> Void
) { ) {
if !forceCreateNewSession,
let doc = metadata[.documentID],
!doc.isEmpty
{
Intelligents.qlClient.fetch(query: GetCopilotSessionsQuery(
workspaceId: .init(stringLiteral: metadata[.workspaceID] ?? ""),
docId: .init(stringLiteral: doc),
options: .some(QueryChatSessionsInput(InputDict([
"action": false,
])))
)) { result in
switch result {
case let .success(value):
if let result = value.data,
let currentUser = result.__data._data["currentUser"] as? DataDict,
let copilot = currentUser._data["copilot"] as? DataDict,
let sessions = copilot._data["sessions"] as? [DataDict],
let mostRecent = sessions.last,
let sessionID = mostRecent._data["id"] as? String
{
print("[*] using existing session", sessionID)
self.dispatchToMain { onSuccess(sessionID) }
return
}
self.chat_createSession(
forceCreateNewSession: true,
onSuccess: onSuccess,
onFailure: onFailure
)
case let .failure(error):
self.dispatchToMain { onFailure(error) }
}
}
}
Intelligents.qlClient.perform( Intelligents.qlClient.perform(
mutation: CreateCopilotSessionMutation(options: .init( mutation: CreateCopilotSessionMutation(options: .init(
docId: metadata[.documentID] ?? "", docId: metadata[.documentID] ?? "",
@@ -103,13 +223,7 @@ private extension IntelligentsChatController {
self.dispatchToMain { onSuccess(session) } self.dispatchToMain { onSuccess(session) }
} else { } else {
self.dispatchToMain { self.dispatchToMain {
onFailure( onFailure(UnableTo.createSession)
NSError(
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "No session created"]
)
)
} }
} }
case let .failure(error): case let .failure(error):
@@ -122,11 +236,15 @@ private extension IntelligentsChatController {
let text = viewModel.text let text = viewModel.text
// let images = viewModel.attachments // let images = viewModel.attachments
let assistantContentID = UUID()
dispatchToMain { dispatchToMain {
let content = ChatContent.user(document: text) let content = ChatContent.user(document: text)
let key = UUID() self.simpleChatContents.updateValue(content, forKey: .init())
self.simpleChatContents.updateValue(content, forKey: key) self.simpleChatContents.updateValue(
self.tableView.scrollLastCellToTop() .assistant(document: "..."),
forKey: assistantContentID
)
self.tableView.scrollToBottomOnNextUpdate = true
} }
let sem = DispatchSemaphore(value: 0) let sem = DispatchSemaphore(value: 0)
@@ -134,6 +252,12 @@ private extension IntelligentsChatController {
Intelligents.qlClient.perform( Intelligents.qlClient.perform(
mutation: CreateCopilotMessageMutation(options: .init( mutation: CreateCopilotMessageMutation(options: .init(
content: .init(stringLiteral: text), content: .init(stringLiteral: text),
params: .some(.dictionary([
"docs": [
"docId": metadata[.documentID] ?? "",
"docContent": metadata[.content] ?? "",
],
])),
sessionId: sessionID sessionId: sessionID
)), )),
queue: .global() queue: .global()
@@ -143,13 +267,13 @@ private extension IntelligentsChatController {
case let .success(value): case let .success(value):
if let messageID = value.data?.createCopilotMessage { if let messageID = value.data?.createCopilotMessage {
print("[*] messageID", messageID) print("[*] messageID", messageID)
self.chat_processWithMessageID(sessionID: sessionID, messageID: messageID) self.chat_processWithMessageID(
sessionID: sessionID,
messageID: messageID,
cellID: assistantContentID
)
} else { } else {
self.chat_onError(NSError( self.chat_onError(UnableTo.createMessage)
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "No message created"]
))
} }
case let .failure(error): case let .failure(error):
self.chat_onError(error) self.chat_onError(error)
@@ -159,7 +283,7 @@ private extension IntelligentsChatController {
sem.wait() sem.wait()
} }
func chat_processWithMessageID(sessionID: String, messageID: String) { func chat_processWithMessageID(sessionID: String, messageID: String, cellID: UUID) {
let url = Constant.affineUpstreamURL let url = Constant.affineUpstreamURL
.appendingPathComponent("api") .appendingPathComponent("api")
.appendingPathComponent("copilot") .appendingPathComponent("copilot")
@@ -171,19 +295,14 @@ private extension IntelligentsChatController {
guard let url = comps?.url else { guard let url = comps?.url else {
assertionFailure() assertionFailure()
chat_onError(NSError( chat_onError(UnableTo.createMessage)
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "No message created"]
))
return return
} }
let contentIdentifier = UUID()
dispatchToMain { dispatchToMain {
self.simpleChatContents.updateValue( self.simpleChatContents.updateValue(
.assistant(document: "..."), .assistant(document: "..."),
forKey: contentIdentifier forKey: cellID
) )
} }
@@ -207,7 +326,7 @@ private extension IntelligentsChatController {
self.dispatchToMain { self.dispatchToMain {
document += message.data document += message.data
let content = ChatContent.assistant(document: document) let content = ChatContent.assistant(document: document)
self.simpleChatContents.updateValue(content, forKey: contentIdentifier) self.simpleChatContents.updateValue(content, forKey: cellID)
} }
} }
let eventSource = EventSource(config: .init(handler: eventHandler, url: url)) let eventSource = EventSource(config: .init(handler: eventHandler, url: url))

View File

@@ -28,6 +28,22 @@ extension IntelligentsChatController {
fatalError() fatalError()
} }
override var isUserInteractionEnabled: Bool {
didSet { updateAvailabilityStyles() }
}
func updateAvailabilityStyles() {
if isUserInteractionEnabled {
backButton.isEnabled = true
dropMenu.isEnabled = true
moreMenu.isEnabled = true
} else {
backButton.isEnabled = false
dropMenu.isEnabled = false
moreMenu.isEnabled = false
}
}
@objc func navigateActionBack() { @objc func navigateActionBack() {
parentViewController?.dismissInContext() parentViewController?.dismissInContext()
} }

View File

@@ -30,9 +30,7 @@ public class IntelligentsChatController: UIViewController {
didSet { updateContentToPublisher() } didSet { updateContentToPublisher() }
} }
var sessionID: String = "" { var sessionID: String = ""
didSet { print("[*] new sessionID: \(sessionID)") }
}
public enum MetadataKey: String { public enum MetadataKey: String {
case documentID case documentID
@@ -84,10 +82,19 @@ public class IntelligentsChatController: UIViewController {
view.addSubview(progressView) view.addSubview(progressView)
setupLayout() setupLayout()
header.moreMenu.showsMenuAsPrimaryAction = true
header.moreMenu.menu = .init(children: [
UIAction(title: "Clear History".localized(), image: UIImage(systemName: "eraser")) { [weak self] _ in
self?.chat_clearHistory()
},
])
// TODO: IMPL // TODO: IMPL
header.dropMenu.isHidden = true
inputBox.editor.controlBanner.cameraButton.isHidden = true inputBox.editor.controlBanner.cameraButton.isHidden = true
inputBox.editor.controlBanner.photoButton.isHidden = true inputBox.editor.controlBanner.photoButton.isHidden = true
updateContentToPublisher()
chat_onLoad() chat_onLoad()
} }
@@ -126,6 +133,10 @@ public class IntelligentsChatController: UIViewController {
action: #selector(chat_onSend), action: #selector(chat_onSend),
for: .touchUpInside for: .touchUpInside
) )
inputBox.editor.submitAction = { [weak self] in
guard let self else { return }
chat_onSend()
}
progressView.hidesWhenStopped = true progressView.hidesWhenStopped = true
progressView.stopAnimating() progressView.stopAnimating()

View File

@@ -28,7 +28,7 @@ extension MessageListView {
.eraseToAnyPublisher() .eraseToAnyPublisher()
// after so, limit the refresh rate so we can handle them better // after so, limit the refresh rate so we can handle them better
let updateQueue = DispatchQueue(label: "flowdown.message-list-update-queue", qos: .userInteractive) let updateQueue = DispatchQueue(label: "affine.message-list-update-queue", qos: .userInteractive)
let inQueuePublisher = publisher let inQueuePublisher = publisher
.throttle(for: .seconds(1 / 5), scheduler: updateQueue, latest: true) .throttle(for: .seconds(1 / 5), scheduler: updateQueue, latest: true)
.eraseToAnyPublisher() .eraseToAnyPublisher()
@@ -49,7 +49,7 @@ extension MessageListView {
private func pickupElementsPair() -> (oldValue: Elements, newValue: Elements)? { private func pickupElementsPair() -> (oldValue: Elements, newValue: Elements)? {
#if DEBUG // just make sure assert is not called in release mode #if DEBUG // just make sure assert is not called in release mode
assert(!elementUpdateProcessLock.try(), "Should not call this method without lock") assert(!elementUpdateProcessLock.try(), "should not call this method without lock")
#endif #endif
guard let distributedPendingUpdateElements else { return nil } guard let distributedPendingUpdateElements else { return nil }
@@ -78,6 +78,11 @@ extension MessageListView {
self.tableView.layoutIfNeeded() self.tableView.layoutIfNeeded()
} }
tableView.contentOffset = contentOffset tableView.contentOffset = contentOffset
if scrollToBottomOnNextUpdate {
scrollToBottomOnNextUpdate = false
scrollToBottom(useTableViewAnimation: false)
}
} }
func reconfigure(enforceReload: Bool) { func reconfigure(enforceReload: Bool) {
@@ -122,20 +127,20 @@ extension MessageListView {
perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5) perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5)
} }
func scrollLastCellToTop(useTableViewAnimation: Bool = false) { // func scrollLastCellToTop(useTableViewAnimation: Bool = false) {
guard elements.count > 1 else { return } // guard elements.count > 1 else { return }
guard tableView.contentSize.height > tableView.frame.height else { return } // guard tableView.contentSize.height > tableView.frame.height else { return }
UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) { // UIView.animate(withDuration: 0.35, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.8) {
self.tableView.scrollToRow( // self.tableView.scrollToRow(
at: IndexPath(row: self.elements.count - 1, section: 0), // at: IndexPath(row: self.elements.count - 1, section: 0),
at: .top, // at: .top,
animated: useTableViewAnimation // animated: useTableViewAnimation
) // )
} // }
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(finishAutomaticScroll), object: nil) // NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(finishAutomaticScroll), object: nil)
isAutomaticScrollAnimating = true // isAutomaticScrollAnimating = true
perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5) // perform(#selector(finishAutomaticScroll), with: nil, afterDelay: 0.5)
} // }
@objc private func finishAutomaticScroll() { @objc private func finishAutomaticScroll() {
isAutomaticScrollAnimating = false isAutomaticScrollAnimating = false

View File

@@ -23,8 +23,9 @@ class MessageListView: UIView {
let elementUpdateProcessLock = NSLock() let elementUpdateProcessLock = NSLock()
var distributedPendingUpdateElements: Elements? = nil var distributedPendingUpdateElements: Elements? = nil
var isAutomaticScrollAnimating: Bool = false var isAutomaticScrollAnimating: Bool = false
var scrollToBottomOnNextUpdate = false
let footerView = UIView(frame: .init(x: 0, y: 0, width: 0, height: 500)) let footerView = UIView(frame: .init(x: 0, y: 0, width: 0, height: 200))
init(dataPublisher: AnyPublisher<[Element], Never>) { init(dataPublisher: AnyPublisher<[Element], Never>) {
super.init(frame: .zero) super.init(frame: .zero)

View File

@@ -15,6 +15,8 @@ extension IntelligentsEphemeralActionController {
chatTask?.stop() chatTask?.stop()
chatTask = nil chatTask = nil
copilotDocumentStorage = "" copilotDocumentStorage = ""
sessionID = ""
messageID = ""
chat_createSession( chat_createSession(
documentIdentifier: documentID, documentIdentifier: documentID,
workspaceIdentifier: workspaceID workspaceIdentifier: workspaceID
@@ -35,18 +37,12 @@ extension IntelligentsEphemeralActionController {
onFailure: @escaping (Error) -> Void onFailure: @escaping (Error) -> Void
) { ) {
if documentIdentifier.isEmpty || workspaceIdentifier.isEmpty { if documentIdentifier.isEmpty || workspaceIdentifier.isEmpty {
onFailure( onFailure(UnableTo.identifyDocumentOrWorkspace)
NSError(
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "Unable to identify the document or workspace"]
)
)
} }
Intelligents.qlClient.perform( Intelligents.qlClient.perform(
mutation: CreateCopilotSessionMutation(options: .init( mutation: CreateCopilotSessionMutation(options: .init(
docId: documentIdentifier, docId: documentIdentifier,
promptName: ation.prompt.rawValue, promptName: action.prompt.rawValue,
workspaceId: workspaceIdentifier workspaceId: workspaceIdentifier
)), )),
queue: .global() queue: .global()
@@ -57,13 +53,7 @@ extension IntelligentsEphemeralActionController {
DispatchQueue.main.async { onSuccess(session) } DispatchQueue.main.async { onSuccess(session) }
} else { } else {
DispatchQueue.main.async { DispatchQueue.main.async {
onFailure( onFailure(UnableTo.createSession)
NSError(
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "No session created"]
)
)
} }
} }
case let .failure(error): case let .failure(error):
@@ -73,9 +63,17 @@ extension IntelligentsEphemeralActionController {
} }
func beginThisRound() { func beginThisRound() {
let parms: [String: AnyHashable] = switch action {
case let .translate(lang):
["language": lang.rawValue]
case .summarize:
[:]
}
let json = try! CustomJSON(_jsonValue: parms)
Intelligents.qlClient.perform( Intelligents.qlClient.perform(
mutation: CreateCopilotMessageMutation(options: .init( mutation: CreateCopilotMessageMutation(options: .init(
content: .init(stringLiteral: "\(documentContent)"), content: .init(stringLiteral: "\(documentContent)"),
params: .some(json),
sessionId: sessionID sessionId: sessionID
)), )),
queue: .global() queue: .global()
@@ -83,8 +81,12 @@ extension IntelligentsEphemeralActionController {
switch result { switch result {
case let .success(value): case let .success(value):
if let messageID = value.data?.createCopilotMessage { if let messageID = value.data?.createCopilotMessage {
print("[*] messageID", messageID) self.messageID = messageID
self.chat_processWithMessageID(sessionID: self.sessionID, messageID: messageID) self.chat_processWithMessageID(sessionID: self.sessionID, messageID: messageID)
} else {
self.presentError(UnableTo.createMessage) {
self.close()
}
} }
case let .failure(error): case let .failure(error):
self.presentError(error) { self.presentError(error) {
@@ -106,11 +108,7 @@ extension IntelligentsEphemeralActionController {
guard let url = comps?.url else { guard let url = comps?.url else {
assertionFailure() assertionFailure()
presentError(NSError( presentError(UnableTo.createMessage)
domain: "Intelligents",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "No message created"]
))
return return
} }

View File

@@ -11,7 +11,7 @@ import MarkdownView
import UIKit import UIKit
public class IntelligentsEphemeralActionController: UIViewController { public class IntelligentsEphemeralActionController: UIViewController {
let ation: EphemeralAction let action: EphemeralAction
let scrollView = UIScrollView() let scrollView = UIScrollView()
let stackView = UIStackView() let stackView = UIStackView()
@@ -28,7 +28,13 @@ public class IntelligentsEphemeralActionController: UIViewController {
public var documentID: String = "" public var documentID: String = ""
public var workspaceID: String = "" public var workspaceID: String = ""
public var documentContent: String = "" public var documentContent: String = ""
var sessionID: String = "" public internal(set) var sessionID: String = "" {
didSet { print(#fileID, #function, sessionID) }
}
public internal(set) var messageID: String = "" {
didSet { print(#fileID, #function, messageID) }
}
var chatTask: EventSource? var chatTask: EventSource?
var copilotDocumentStorage: String = "" { var copilotDocumentStorage: String = "" {
@@ -39,7 +45,7 @@ public class IntelligentsEphemeralActionController: UIViewController {
} }
public init(action: EphemeralAction) { public init(action: EphemeralAction) {
ation = action self.action = action
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
title = action.title title = action.title
} }
@@ -121,6 +127,10 @@ public class IntelligentsEphemeralActionController: UIViewController {
actionBar.retryButton.action = { [weak self] in actionBar.retryButton.action = { [weak self] in
self?.beginAction() self?.beginAction()
} }
actionBar.continueToChat.action = { [weak self] in
guard let self else { return }
continueToChat()
}
} }
func setupContentViews() { func setupContentViews() {
@@ -275,3 +285,13 @@ public class IntelligentsEphemeralActionController: UIViewController {
) { self.scrollView.setContentOffset(bottomOffset, animated: false) } ) { self.scrollView.setContentOffset(bottomOffset, animated: false) }
} }
} }
extension IntelligentsEphemeralActionController {
func continueToChat() {
let chatController = IntelligentsChatController()
chatController.metadata[.documentID] = documentID
chatController.metadata[.workspaceID] = workspaceID
chatController.metadata[.content] = documentContent
navigationController?.pushViewController(chatController, animated: true)
}
}

View File

@@ -9,62 +9,62 @@ import SwiftUI
@main @main
struct TheApp: App { struct TheApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
NavigationView { NavigationView {
Content() Content()
.navigationTitle("MarkdownView") .navigationTitle("MarkdownView")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
.navigationViewStyle(.stack) .navigationViewStyle(.stack)
}
} }
}
} }
import MarkdownParser import MarkdownParser
import MarkdownView import MarkdownView
class ContentController: UIViewController { class ContentController: UIViewController {
let document = MarkdownParser().feed(testDocument) let document = MarkdownParser().feed(testDocument)
let scrollView = UIScrollView() let scrollView = UIScrollView()
let markdownView = MarkdownView(theme: .default) let markdownView = MarkdownView(theme: .default)
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
view.addSubview(scrollView) view.addSubview(scrollView)
scrollView.addSubview(markdownView) scrollView.addSubview(markdownView)
} }
override func viewWillLayoutSubviews() { override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews() super.viewWillLayoutSubviews()
scrollView.frame = view.bounds scrollView.frame = view.bounds
let width = view.bounds.width - 32 let width = view.bounds.width - 32
let manifest = document.map { let manifest = document.map {
let manifest = $0.manifest(theme: markdownView.theme) let manifest = $0.manifest(theme: markdownView.theme)
manifest.setLayoutWidth(width) manifest.setLayoutWidth(width)
manifest.layoutIfNeeded() manifest.layoutIfNeeded()
return manifest return manifest
}
markdownView.updateContentViews(manifest)
markdownView.frame = .init(
x: 16,
y: 16,
width: width,
height: markdownView.height
)
scrollView.contentSize = .init(
width: width,
height: markdownView.height + 100
)
} }
markdownView.updateContentViews(manifest)
markdownView.frame = .init(
x: 16,
y: 16,
width: width,
height: markdownView.height
)
scrollView.contentSize = .init(
width: width,
height: markdownView.height + 100
)
}
} }
struct Content: UIViewControllerRepresentable { struct Content: UIViewControllerRepresentable {
func makeUIViewController(context _: Context) -> ContentController { func makeUIViewController(context _: Context) -> ContentController {
ContentController() ContentController()
} }
func updateUIViewController(_: ContentController, context _: Context) {} func updateUIViewController(_: ContentController, context _: Context) {}
} }
let testDocument = ###""" let testDocument = ###"""

View File

@@ -15,7 +15,7 @@ extension UIView {
} }
var shouldRemovedIndices: [Int] = [] var shouldRemovedIndices: [Int] = []
defer { defer {
shouldRemovedIndices.sorted(by: >).forEach { index in for index in shouldRemovedIndices.sorted(by: >) {
blockViews.remove(at: index) blockViews.remove(at: index)
} }
} }

View File

@@ -2,7 +2,12 @@
"schemaNamespace": "AffineGraphQL", "schemaNamespace": "AffineGraphQL",
"input": { "input": {
"operationSearchPaths": ["../../../common/graphql/src/graphql/**/*.gql"], "operationSearchPaths": ["../../../common/graphql/src/graphql/**/*.gql"],
"schemaSearchPaths": ["../../../backend/server/src/schema.gql"] "schemaSearchPaths": ["../../../backend/server/src/schema.gql"],
"schemaCustomization": {
"customTypeNames": {
"JSON": "[String: Any]"
}
}
}, },
"output": { "output": {
"testMocks": { "testMocks": {