mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
feat: ai now working again (#13196)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for displaying title and summary fields in workspace pages. * Introduced a menu in the chat header with a "Clear History" option to remove chat history. * **Improvements** * Enhanced chat message handling with asynchronous context preparation and improved markdown processing. * Simplified chat input and assistant message rendering for better performance and maintainability. * Updated dependency versions for improved stability and compatibility. * **Bug Fixes** * Ensured chat features are available in all build configurations, not just debug mode. * **Chores** * Removed unused dependencies and internal code, and disabled certain function bar options. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -27,15 +27,6 @@
|
||||
"version" : "1.1.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "litext",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/Litext",
|
||||
"state" : {
|
||||
"revision" : "c37f3ab5826659854311e20d6c3942d4905b00b6",
|
||||
"version" : "0.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lrucache",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -50,8 +41,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/MarkdownView",
|
||||
"state" : {
|
||||
"revision" : "29a9da19d6dc21af4e629c423961b0f453ffe192",
|
||||
"version" : "2.3.8"
|
||||
"revision" : "446dba45be81c67d0717d19277367dcbe5b2fb12",
|
||||
"version" : "3.1.9"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -68,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Lakr233/Splash",
|
||||
"state" : {
|
||||
"revision" : "4d997712fe07f75695aacdf287aeb3b1f2c6ab88",
|
||||
"version" : "0.17.0"
|
||||
"revision" : "de9cde249fdb7a173a6e6b950ab18b11f6c2a557",
|
||||
"version" : "0.18.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,23 +13,5 @@ extension AFFiNEViewController: IntelligentsButtonDelegate {
|
||||
// if it shows up then we are ready to go
|
||||
let controller = IntelligentsController()
|
||||
self.present(controller, animated: true)
|
||||
// IntelligentContext.shared.webView = webView
|
||||
// button.beginProgress()
|
||||
// IntelligentContext.shared.preparePresent { result in
|
||||
// DispatchQueue.main.async {
|
||||
// button.stopProgress()
|
||||
// switch result {
|
||||
// case .success:
|
||||
// case let .failure(failure):
|
||||
// let alert = UIAlertController(
|
||||
// title: "Error",
|
||||
// message: failure.localizedDescription,
|
||||
// preferredStyle: .alert
|
||||
// )
|
||||
// alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||
// self.present(alert, animated: true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +64,7 @@ class AFFiNEViewController: CAPBridgeViewController {
|
||||
switch result {
|
||||
case .failure: break
|
||||
case .success:
|
||||
#if DEBUG
|
||||
// only show the button in debug mode before we get done
|
||||
self.presentIntelligentsButton()
|
||||
#else
|
||||
break
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getCopilotRecentSessions"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: $limit } options: { fork: false, sessionOrder: desc, withMessages: true } ) { __typename ...PaginatedCopilotChats } } } }"#,
|
||||
#"query getCopilotRecentSessions($workspaceId: String!, $limit: Int = 10) { currentUser { __typename copilot(workspaceId: $workspaceId) { __typename chats( pagination: { first: $limit } options: { fork: false, sessionOrder: desc, withMessages: false } ) { __typename ...PaginatedCopilotChats } } } }"#,
|
||||
fragments: [CopilotChatHistory.self, CopilotChatMessage.self, PaginatedCopilotChats.self]
|
||||
))
|
||||
|
||||
@@ -69,7 +69,7 @@ public class GetCopilotRecentSessionsQuery: GraphQLQuery {
|
||||
"options": [
|
||||
"fork": false,
|
||||
"sessionOrder": "desc",
|
||||
"withMessages": true
|
||||
"withMessages": false
|
||||
]
|
||||
]),
|
||||
] }
|
||||
|
||||
@@ -7,7 +7,7 @@ public class GetWorkspacePageByIdQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getWorkspacePageById"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getWorkspacePageById($workspaceId: String!, $pageId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $pageId) { __typename id mode defaultRole public } } }"#
|
||||
#"query getWorkspacePageById($workspaceId: String!, $pageId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $pageId) { __typename id mode defaultRole public title summary } } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
@@ -68,12 +68,16 @@ public class GetWorkspacePageByIdQuery: GraphQLQuery {
|
||||
.field("mode", GraphQLEnum<AffineGraphQL.PublicDocMode>.self),
|
||||
.field("defaultRole", GraphQLEnum<AffineGraphQL.DocRole>.self),
|
||||
.field("public", Bool.self),
|
||||
.field("title", String?.self),
|
||||
.field("summary", String?.self),
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var mode: GraphQLEnum<AffineGraphQL.PublicDocMode> { __data["mode"] }
|
||||
public var defaultRole: GraphQLEnum<AffineGraphQL.DocRole> { __data["defaultRole"] }
|
||||
public var `public`: Bool { __data["public"] }
|
||||
public var title: String? { __data["title"] }
|
||||
public var summary: String? { __data["summary"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ let package = Package(
|
||||
.package(url: "https://github.com/SwifterSwift/SwifterSwift.git", from: "6.0.0"),
|
||||
.package(url: "https://github.com/Recouse/EventSource", from: "0.1.4"),
|
||||
.package(url: "https://github.com/Lakr233/ListViewKit", from: "1.1.6"),
|
||||
.package(url: "https://github.com/Lakr233/MarkdownView", exact: "2.3.8"),
|
||||
.package(url: "https://github.com/Lakr233/MarkdownView", from: "3.1.9"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Intelligents", dependencies: [
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ChatManager+CURD.swift
|
||||
// Intelligents
|
||||
//
|
||||
// Created by 秋星桥 on 7/14/25.
|
||||
//
|
||||
|
||||
import AffineGraphQL
|
||||
import Apollo
|
||||
import ApolloAPI
|
||||
import EventSource
|
||||
import Foundation
|
||||
import MarkdownParser
|
||||
import MarkdownView
|
||||
|
||||
extension ChatManager {
|
||||
func clearCurrentSession() {
|
||||
guard let session = IntelligentContext.shared.currentSession else {
|
||||
print("[-] no current session to clear")
|
||||
return
|
||||
}
|
||||
|
||||
let mutation = CleanupCopilotSessionMutation(input: .init(
|
||||
docId: session.docId ?? "",
|
||||
sessionIds: [session.id],
|
||||
workspaceId: session.workspaceId
|
||||
))
|
||||
|
||||
QLService.shared.client.perform(mutation: mutation) { result in
|
||||
print("[+] cleanup session result: \(result)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,53 +24,173 @@ private extension InputBoxData {
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatManager {
|
||||
public func startUserRequest(
|
||||
content: String,
|
||||
inputBoxData: InputBoxData,
|
||||
sessionId: String
|
||||
) {
|
||||
public extension ChatManager {
|
||||
func startUserRequest(editorData: InputBoxData, sessionId: String) {
|
||||
append(sessionId: sessionId, UserMessageCellViewModel(
|
||||
id: .init(),
|
||||
content: inputBoxData.text,
|
||||
content: editorData.text,
|
||||
timestamp: .init()
|
||||
))
|
||||
append(sessionId: sessionId, UserHintCellViewModel(
|
||||
id: .init(),
|
||||
timestamp: .init(),
|
||||
imageAttachments: inputBoxData.imageAttachments,
|
||||
fileAttachments: inputBoxData.fileAttachments,
|
||||
docAttachments: inputBoxData.documentAttachments
|
||||
imageAttachments: editorData.imageAttachments,
|
||||
fileAttachments: editorData.fileAttachments,
|
||||
docAttachments: editorData.documentAttachments
|
||||
))
|
||||
|
||||
let viewModelId = append(sessionId: sessionId, AssistantMessageCellViewModel(
|
||||
id: .init(),
|
||||
content: "...",
|
||||
timestamp: .init()
|
||||
))
|
||||
scrollToBottomPublisher.send(sessionId)
|
||||
|
||||
guard let workspaceId = IntelligentContext.shared.currentWorkspaceId,
|
||||
!workspaceId.isEmpty
|
||||
else {
|
||||
report(sessionId, ChatError.unknownError)
|
||||
assertionFailure("Invalid workspace ID")
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
self.prepareContext(
|
||||
workspaceId: workspaceId,
|
||||
sessionId: sessionId,
|
||||
editorData: editorData,
|
||||
viewModelId: viewModelId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ChatManager {
|
||||
func prepareContext(
|
||||
workspaceId: String,
|
||||
sessionId: String,
|
||||
editorData: InputBoxData,
|
||||
viewModelId: UUID
|
||||
) {
|
||||
assert(!Thread.isMainThread)
|
||||
let createContext = CreateCopilotContextMutation(
|
||||
workspaceId: workspaceId,
|
||||
sessionId: sessionId
|
||||
)
|
||||
QLService.shared.client.perform(mutation: createContext) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case let .success(graphQLResult):
|
||||
guard let contextId = graphQLResult.data?.createCopilotContext else {
|
||||
self.report(sessionId, ChatError.invalidResponse)
|
||||
return
|
||||
}
|
||||
print("[+] copilot context created: \(contextId)")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let docAttachGroup = DispatchGroup()
|
||||
for docAttach in editorData.documentAttachments {
|
||||
let addDoc = AddContextDocMutation(
|
||||
options: .init(
|
||||
contextId: contextId,
|
||||
docId: docAttach.documentID
|
||||
)
|
||||
)
|
||||
docAttachGroup.enter()
|
||||
QLService.shared.client.perform(mutation: addDoc) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
print("[+] doc \(docAttach.documentID) added to context")
|
||||
case let .failure(error):
|
||||
print("[-] addContextDoc failed: \(error)")
|
||||
}
|
||||
docAttachGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
docAttachGroup.notify(queue: .global()) {
|
||||
var contextSnippet = ""
|
||||
if !editorData.documentAttachments.isEmpty {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
let matchQuery = MatchContextQuery(
|
||||
contextId: .some(contextId),
|
||||
workspaceId: .some(workspaceId),
|
||||
content: editorData.text,
|
||||
limit: .none,
|
||||
scopedThreshold: .none,
|
||||
threshold: .none
|
||||
)
|
||||
QLService.shared.client.fetch(query: matchQuery) { result in
|
||||
switch result {
|
||||
case let .success(queryResult):
|
||||
let matches = queryResult.data?.currentUser?.copilot.contexts ?? []
|
||||
let matchDocs = matches.compactMap(\.matchWorkspaceDocs).flatMap(\.self)
|
||||
for context in matchDocs {
|
||||
contextSnippet += "<file docId=\"\(context.docId)\" chunk=\"\(context.chunk)\">\(context.content)</file>\n"
|
||||
}
|
||||
case let .failure(error):
|
||||
print("[-] matchContext failed: \(error)")
|
||||
// self.report(sessionId, error)
|
||||
}
|
||||
sem.signal()
|
||||
}
|
||||
sem.wait()
|
||||
}
|
||||
print("[+] context snippet prepared: \(contextSnippet)")
|
||||
self.startCopilotResponse(
|
||||
editorData: editorData,
|
||||
contextSnippet: contextSnippet,
|
||||
sessionId: sessionId,
|
||||
viewModelId: viewModelId
|
||||
)
|
||||
}
|
||||
}
|
||||
case let .failure(error):
|
||||
self.report(sessionId, error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startCopilotResponse(
|
||||
editorData: InputBoxData,
|
||||
contextSnippet: String,
|
||||
sessionId: String,
|
||||
viewModelId: UUID
|
||||
) {
|
||||
assert(!Thread.isMainThread)
|
||||
|
||||
let messageParameters: [String: AnyHashable] = [
|
||||
// packages/frontend/core/src/blocksuite/ai/provider/setup-provider.tsx
|
||||
"docs": inputBoxData.documentAttachments.map(\.documentID), // affine doc
|
||||
"docs": editorData.documentAttachments.map(\.documentID), // affine doc
|
||||
"files": [String](), // attachment in context, keep nil for now
|
||||
"searchMode": inputBoxData.isSearchEnabled ? "MUST" : "AUTO",
|
||||
"searchMode": editorData.isSearchEnabled ? "MUST" : "AUTO",
|
||||
]
|
||||
let uploadableAttachments: [GraphQLFile] = [
|
||||
inputBoxData.fileAttachments.map { file -> GraphQLFile in
|
||||
.init(
|
||||
fieldName: file.name,
|
||||
originalName: file.name,
|
||||
data: file.data ?? .init()
|
||||
)
|
||||
let attachmentFieldName = "options.blobs"
|
||||
var uploadableAttachments: [GraphQLFile] = [
|
||||
editorData.fileAttachments.map { file -> GraphQLFile in
|
||||
.init(fieldName: attachmentFieldName, originalName: file.name, data: file.data ?? .init())
|
||||
},
|
||||
inputBoxData.imageAttachments.map { image -> GraphQLFile in
|
||||
.init(
|
||||
fieldName: image.hashValue.description,
|
||||
originalName: "image.jpg",
|
||||
data: image.imageData
|
||||
)
|
||||
editorData.imageAttachments.map { image -> GraphQLFile in
|
||||
.init(fieldName: attachmentFieldName, originalName: "image.jpg", data: image.imageData)
|
||||
},
|
||||
].flatMap(\.self)
|
||||
assert(uploadableAttachments.allSatisfy { !($0.data?.isEmpty ?? true) })
|
||||
// in Apollo, filed name is handled as attached object to field when there is only one attachment
|
||||
// to use array on our server, we need to append a dummy attachment
|
||||
// which is ignored if data is empty and name is empty
|
||||
if uploadableAttachments.count == 1 {
|
||||
uploadableAttachments.append(.init(fieldName: attachmentFieldName, originalName: "", data: .init()))
|
||||
}
|
||||
guard let input = try? CreateChatMessageInput(
|
||||
content: .some(content),
|
||||
attachments: [],
|
||||
blobs: .some([]), // must have the placeholder
|
||||
content: .some(contextSnippet.isEmpty ? editorData.text : "\(contextSnippet)\n\(editorData.text)"),
|
||||
params: .some(AffineGraphQL.JSON(_jsonValue: messageParameters)),
|
||||
sessionId: sessionId
|
||||
) else {
|
||||
report(sessionId, ChatError.unknownError)
|
||||
assertionFailure() // very unlikely to happen
|
||||
return
|
||||
}
|
||||
@@ -83,11 +203,6 @@ extension ChatManager {
|
||||
self.report(sessionId, ChatError.invalidResponse)
|
||||
return
|
||||
}
|
||||
let viewModelId = self.append(sessionId: sessionId, AssistantMessageCellViewModel(
|
||||
id: .init(),
|
||||
content: .init(),
|
||||
timestamp: .init()
|
||||
))
|
||||
self.startStreamingResponse(
|
||||
sessionId: sessionId,
|
||||
messageId: messageIdentifier,
|
||||
@@ -99,8 +214,10 @@ extension ChatManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startStreamingResponse(sessionId: String, messageId: String, applyingTo vmId: UUID) {
|
||||
private extension ChatManager {
|
||||
func startStreamingResponse(sessionId: String, messageId: String, applyingTo vmId: UUID) {
|
||||
let base = IntelligentContext.shared.webViewMetadata[.currentServerBaseUrl] as? String
|
||||
guard let base, let url = URL(string: base) else {
|
||||
report(sessionId, ChatError.invalidServerConfiguration)
|
||||
@@ -164,24 +281,11 @@ extension ChatManager {
|
||||
vmId: UUID
|
||||
) {
|
||||
let result = MarkdownParser().parse(document)
|
||||
var renderedContexts: [String: RenderedItem] = [:]
|
||||
for (key, value) in result.mathContext {
|
||||
let image = MathRenderer.renderToImage(
|
||||
latex: value,
|
||||
fontSize: MarkdownTheme.default.fonts.body.pointSize,
|
||||
textColor: MarkdownTheme.default.colors.body
|
||||
)?.withRenderingMode(.alwaysTemplate)
|
||||
let renderedContext = RenderedItem(
|
||||
image: image,
|
||||
text: value
|
||||
)
|
||||
renderedContexts["math://\(key)"] = renderedContext
|
||||
}
|
||||
let content = MarkdownTextView.PreprocessContent(parserResult: result, theme: .default)
|
||||
|
||||
with(sessionId: sessionId, vmId: vmId) { (viewModel: inout AssistantMessageCellViewModel) in
|
||||
viewModel.content = document
|
||||
viewModel.documentBlocks = result.document
|
||||
viewModel.documentRenderedContent = renderedContexts
|
||||
viewModel.preprocessedContent = content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ public class ChatManager: ObservableObject, @unchecked Sendable {
|
||||
closable.removeAll()
|
||||
}
|
||||
|
||||
public func clearAll() {
|
||||
assert(Thread.isMainThread)
|
||||
closeAll()
|
||||
viewModels.removeAll()
|
||||
}
|
||||
|
||||
public func with(sessionId: String, _ action: (inout OrderedDictionary<MessageID, any ChatCellViewModel>) -> Void) {
|
||||
if Thread.isMainThread {
|
||||
if var sessionViewModels = viewModels[sessionId] {
|
||||
@@ -59,8 +65,6 @@ public class ChatManager: ObservableObject, @unchecked Sendable {
|
||||
return
|
||||
}
|
||||
sessionViewModels[vmId] = vm
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +85,7 @@ extension MainViewController: InputBoxDelegate {
|
||||
}
|
||||
|
||||
ChatManager.shared.closeAll()
|
||||
ChatManager.shared.startUserRequest(
|
||||
content: inputData.text,
|
||||
inputBoxData: inputData,
|
||||
sessionId: currentSession.id
|
||||
)
|
||||
ChatManager.shared.startUserRequest(editorData: inputData, sessionId: currentSession.id)
|
||||
}
|
||||
|
||||
private func showAlert(title: String, message: String) {
|
||||
|
||||
@@ -21,11 +21,6 @@ class AssistantMessageCell: ChatBaseCell {
|
||||
contentView.addSubview(markdownView)
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
markdownView.prepareForReuse()
|
||||
}
|
||||
|
||||
override func configure(with viewModel: any ChatCellViewModel) {
|
||||
super.configure(with: viewModel)
|
||||
|
||||
@@ -33,10 +28,7 @@ class AssistantMessageCell: ChatBaseCell {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
markdownView.setMarkdown(
|
||||
vm.documentBlocks,
|
||||
renderedContent: vm.documentRenderedContent
|
||||
)
|
||||
markdownView.setMarkdown(vm.preprocessedContent)
|
||||
}
|
||||
|
||||
override func layoutContentView(bounds: CGRect) {
|
||||
@@ -53,10 +45,7 @@ class AssistantMessageCell: ChatBaseCell {
|
||||
markdownViewForSizeCalculation.frame = .init(
|
||||
x: 0, y: 0, width: width, height: .greatestFiniteMagnitude
|
||||
)
|
||||
markdownViewForSizeCalculation.setMarkdown(
|
||||
vm.documentBlocks,
|
||||
renderedContent: vm.documentRenderedContent
|
||||
)
|
||||
markdownViewForSizeCalculation.setMarkdownManually(vm.preprocessedContent)
|
||||
let boundingSize = markdownViewForSizeCalculation.boundingSize(for: width)
|
||||
return ceil(boundingSize.height)
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ struct AssistantMessageCellViewModel: ChatCellViewModel {
|
||||
var citations: [CitationViewModel]?
|
||||
var actions: [MessageActionViewModel]?
|
||||
|
||||
var documentBlocks: [MarkdownBlockNode]
|
||||
var documentRenderedContent: RenderContext
|
||||
var preprocessedContent: MarkdownTextView.PreprocessContent
|
||||
|
||||
init(
|
||||
id: UUID,
|
||||
@@ -53,7 +52,7 @@ struct AssistantMessageCellViewModel: ChatCellViewModel {
|
||||
actions: [MessageActionViewModel]? = nil
|
||||
) {
|
||||
// time expensive rendering should not happen here
|
||||
assert(!Thread.isMainThread || content.isEmpty)
|
||||
assert(!Thread.isMainThread || content.count < 10) // allow placeholder content
|
||||
|
||||
self.id = id
|
||||
self.content = content
|
||||
@@ -67,21 +66,10 @@ struct AssistantMessageCellViewModel: ChatCellViewModel {
|
||||
|
||||
let parser = MarkdownParser()
|
||||
let parserResult = parser.parse(content)
|
||||
documentBlocks = parserResult.document
|
||||
var renderedContexts: [String: RenderedItem] = [:]
|
||||
for (key, value) in parserResult.mathContext {
|
||||
let image = MathRenderer.renderToImage(
|
||||
latex: value,
|
||||
fontSize: MarkdownTheme.default.fonts.body.pointSize,
|
||||
textColor: MarkdownTheme.default.colors.body
|
||||
)?.withRenderingMode(.alwaysTemplate)
|
||||
let renderedContext = RenderedItem(
|
||||
image: image,
|
||||
text: value
|
||||
)
|
||||
renderedContexts["math://\(key)"] = renderedContext
|
||||
}
|
||||
documentRenderedContent = renderedContexts
|
||||
preprocessedContent = MarkdownTextView.PreprocessContent(
|
||||
parserResult: parserResult,
|
||||
theme: .default,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ private let unselectedColor: UIColor = .affineIconPrimary
|
||||
private let selectedColor: UIColor = .affineIconActivated
|
||||
|
||||
private let configurableOptions: [ConfigurableOptions] = [
|
||||
.networking,
|
||||
.reasoning,
|
||||
// .networking,
|
||||
// .reasoning,
|
||||
]
|
||||
enum ConfigurableOptions {
|
||||
case tool
|
||||
|
||||
@@ -22,11 +22,20 @@ class MainHeaderView: UIView {
|
||||
$0.textAlignment = .center
|
||||
}
|
||||
|
||||
private lazy var modelMenu = UIDeferredMenuElement.uncached { completion in
|
||||
completion([])
|
||||
}
|
||||
|
||||
private lazy var dropdownButton = UIButton(type: .system).then {
|
||||
$0.imageView?.contentMode = .scaleAspectFit
|
||||
$0.setImage(UIImage.affineArrowDown, for: .normal)
|
||||
$0.tintColor = UIColor.affineIconPrimary
|
||||
$0.addTarget(self, action: #selector(dropdownButtonTapped), for: .touchUpInside)
|
||||
$0.showsMenuAsPrimaryAction = true
|
||||
$0.menu = UIMenu(options: [.displayInline], children: [
|
||||
modelMenu,
|
||||
])
|
||||
$0.isHidden = true
|
||||
}
|
||||
|
||||
private lazy var centerStackView = UIStackView().then {
|
||||
@@ -45,6 +54,13 @@ class MainHeaderView: UIView {
|
||||
$0.layer.cornerRadius = 8
|
||||
$0.addTarget(self, action: #selector(menuButtonTapped), for: .touchUpInside)
|
||||
$0.setContentHuggingPriority(.required, for: .horizontal)
|
||||
$0.showsMenuAsPrimaryAction = true
|
||||
$0.menu = .init(options: [.displayInline], children: [
|
||||
UIAction(title: "Clear History", image: .affineBroom, handler: { _ in
|
||||
ChatManager.shared.clearCurrentSession()
|
||||
ChatManager.shared.clearAll()
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
private lazy var leftSpacerView = UIView()
|
||||
|
||||
Reference in New Issue
Block a user