diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index a10bf64a2d..1b914cd349 100644 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj +++ b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 505B0A342CEB3FB10092FC35 /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 505B0A332CEB3FB10092FC35 /* Intelligents */; }; 505B0A362CEB48B10092FC35 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B0A352CEB48B10092FC35 /* RootViewController.swift */; }; + 9D1C07272CEC3E9500E1C502 /* IntelligentsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D1C07262CEC3E8200E1C502 /* IntelligentsPlugin.swift */; }; 9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */; }; 9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE172CCB9876006677DB /* CookieManager.swift */; }; 9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE182CCB9876006677DB /* CookiePlugin.swift */; }; @@ -26,6 +27,7 @@ 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 505B0A312CEB3FAB0092FC35 /* Intelligents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Intelligents; sourceTree = ""; }; 505B0A352CEB48B10092FC35 /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; + 9D1C07262CEC3E8200E1C502 /* IntelligentsPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntelligentsPlugin.swift; sourceTree = ""; }; 9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashcashPlugin.swift; sourceTree = ""; }; 9D90BE172CCB9876006677DB /* CookieManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieManager.swift; sourceTree = ""; }; 9D90BE182CCB9876006677DB /* CookiePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookiePlugin.swift; sourceTree = ""; }; @@ -102,6 +104,7 @@ 9D90BE192CCB9876006677DB /* Cookie */ = { isa = PBXGroup; children = ( + 9D1C07262CEC3E8200E1C502 /* IntelligentsPlugin.swift */, 9D90BE172CCB9876006677DB /* CookieManager.swift */, 9D90BE182CCB9876006677DB /* CookiePlugin.swift */, 9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */, @@ -250,6 +253,7 @@ 9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */, 9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */, 505B0A362CEB48B10092FC35 /* RootViewController.swift in Sources */, + 9D1C07272CEC3E9500E1C502 /* IntelligentsPlugin.swift in Sources */, 9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */, 9D90BE272CCB9876006677DB /* AffineViewController.swift in Sources */, 9D90BE282CCB9876006677DB /* AppDelegate.swift in Sources */, diff --git a/packages/frontend/apps/ios/App/App/AffineViewController.swift b/packages/frontend/apps/ios/App/App/AffineViewController.swift index 861a6a7ee8..378cc2888e 100644 --- a/packages/frontend/apps/ios/App/App/AffineViewController.swift +++ b/packages/frontend/apps/ios/App/App/AffineViewController.swift @@ -13,16 +13,16 @@ class AFFiNEViewController: CAPBridgeViewController { override func capacitorDidLoad() { bridge?.registerPluginInstance(CookiePlugin()) bridge?.registerPluginInstance(HashcashPlugin()) + bridge?.registerPluginInstance(IntelligentsPlugin(ui: self)) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) navigationController?.setNavigationBarHidden(false, animated: animated) - self.presentIntelligentsButton() + self.dismissIntelligentsButton() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - dismissIntelligentsButton() } } diff --git a/packages/frontend/apps/ios/App/App/Packages/Intelligents/Sources/Intelligents/IntelligentsButton/IntelligentsButton.swift b/packages/frontend/apps/ios/App/App/Packages/Intelligents/Sources/Intelligents/IntelligentsButton/IntelligentsButton.swift index 1dfe9ce25e..c758204887 100644 --- a/packages/frontend/apps/ios/App/App/Packages/Intelligents/Sources/Intelligents/IntelligentsButton/IntelligentsButton.swift +++ b/packages/frontend/apps/ios/App/App/Packages/Intelligents/Sources/Intelligents/IntelligentsButton/IntelligentsButton.swift @@ -5,7 +5,6 @@ // Created by 秋星桥 on 2024/11/18. // -import ColorfulX import UIKit // floating button to open intelligent panel diff --git a/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift b/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift new file mode 100644 index 0000000000..59d771b5c9 --- /dev/null +++ b/packages/frontend/apps/ios/App/App/Plugins/Cookie/IntelligentsPlugin.swift @@ -0,0 +1,34 @@ +import Foundation +import Capacitor + +@objc(IntelligentsPlugin) +public class IntelligentsPlugin: CAPPlugin, CAPBridgedPlugin { + public let identifier = "IntelligentsPlugin" + public let jsName = "Intelligents" + public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "presentIntelligentsButton", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "dismissIntelligentsButton", returnType: CAPPluginReturnPromise) + ] + public let ui: UIViewController + + init(ui: UIViewController) { + self.ui = ui + super.init() + } + + @objc public func presentIntelligentsButton(_ call: CAPPluginCall) { + DispatchQueue.main.async { + self.ui.presentIntelligentsButton() + print("!!!!!!!!!!!!present") + call.resolve() + } + } + + @objc public func dismissIntelligentsButton(_ call: CAPPluginCall) { + DispatchQueue.main.async { + self.ui.dismissIntelligentsButton() + print("!!!!!!!!!!!!dismiss") + call.resolve() + } + } +} diff --git a/packages/frontend/apps/ios/src/app.tsx b/packages/frontend/apps/ios/src/app.tsx index c981009611..47863507ca 100644 --- a/packages/frontend/apps/ios/src/app.tsx +++ b/packages/frontend/apps/ios/src/app.tsx @@ -3,6 +3,7 @@ import { AppContainer } from '@affine/core/desktop/components/app-container'; import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; import { configureCommonModules } from '@affine/core/modules'; +import { AIButtonProvider } from '@affine/core/modules/ai-button'; import { AuthService, ValidatorProvider, @@ -32,6 +33,7 @@ import { RouterProvider } from 'react-router-dom'; import { configureFetchProvider } from './fetch'; import { Cookie } from './plugins/cookie'; import { Hashcash } from './plugins/hashcash'; +import { Intelligents } from './plugins/intelligents'; const future = { v7_startTransition: true, @@ -76,6 +78,14 @@ framework.impl(ValidatorProvider, { return res.value; }, }); +framework.impl(AIButtonProvider, { + presentAIButton: () => { + return Intelligents.presentIntelligentsButton(); + }, + dismissAIButton: () => { + return Intelligents.dismissIntelligentsButton(); + }, +}); const frameworkProvider = framework.provider(); // setup application lifecycle events, and emit application start event diff --git a/packages/frontend/apps/ios/src/plugins/intelligents/definitions.ts b/packages/frontend/apps/ios/src/plugins/intelligents/definitions.ts new file mode 100644 index 0000000000..b53876c7d7 --- /dev/null +++ b/packages/frontend/apps/ios/src/plugins/intelligents/definitions.ts @@ -0,0 +1,4 @@ +export interface IntelligentsPlugin { + presentIntelligentsButton(): Promise; + dismissIntelligentsButton(): Promise; +} diff --git a/packages/frontend/apps/ios/src/plugins/intelligents/index.ts b/packages/frontend/apps/ios/src/plugins/intelligents/index.ts new file mode 100644 index 0000000000..7acb52298f --- /dev/null +++ b/packages/frontend/apps/ios/src/plugins/intelligents/index.ts @@ -0,0 +1,8 @@ +import { registerPlugin } from '@capacitor/core'; + +import type { IntelligentsPlugin } from './definitions'; + +const Intelligents = registerPlugin('Intelligents'); + +export * from './definitions'; +export { Intelligents }; diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx index 01ce08d1a6..2a0f12dbd7 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx @@ -10,6 +10,7 @@ import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-he import { PageHeader } from '@affine/core/components/mobile'; import { PageDetailEditor } from '@affine/core/components/page-detail-editor'; import { DetailPageWrapper } from '@affine/core/desktop/pages/workspace/detail-page/detail-page-wrapper'; +import { AIButtonService } from '@affine/core/modules/ai-button'; import { EditorService } from '@affine/core/modules/editor'; import { JournalService } from '@affine/core/modules/journal'; import { WorkbenchService } from '@affine/core/modules/workbench'; @@ -56,6 +57,7 @@ const DetailPageImpl = () => { workspaceService, globalContextService, featureFlagService, + aIButtonService, } = useServices({ WorkbenchService, ViewService, @@ -64,6 +66,7 @@ const DetailPageImpl = () => { WorkspaceService, GlobalContextService, FeatureFlagService, + AIButtonService, }); const editor = editorService.editor; const workspace = workspaceService.workspace; @@ -108,6 +111,14 @@ const DetailPageImpl = () => { }; }, [doc, globalContext, mode]); + useEffect(() => { + aIButtonService.presentAIButton(true); + + return () => { + aIButtonService.presentAIButton(false); + }; + }, [aIButtonService]); + useEffect(() => { if (!enableKeyboardToolbar) setDocReadonly(doc.id, true); }, [enableKeyboardToolbar, doc.id, setDocReadonly]); diff --git a/packages/frontend/core/src/modules/ai-button/index.ts b/packages/frontend/core/src/modules/ai-button/index.ts new file mode 100644 index 0000000000..f01dd64af1 --- /dev/null +++ b/packages/frontend/core/src/modules/ai-button/index.ts @@ -0,0 +1,13 @@ +export { AIButtonProvider } from './provider/ai-button'; +export { AIButtonService } from './services/ai-button'; + +import type { Framework } from '@toeverything/infra'; + +import { AIButtonProvider } from './provider/ai-button'; +import { AIButtonService } from './services/ai-button'; + +export const configureAIButtonModule = (framework: Framework) => { + framework.service(AIButtonService, container => { + return new AIButtonService(container.getOptional(AIButtonProvider)); + }); +}; diff --git a/packages/frontend/core/src/modules/ai-button/provider/ai-button.ts b/packages/frontend/core/src/modules/ai-button/provider/ai-button.ts new file mode 100644 index 0000000000..3cee232d59 --- /dev/null +++ b/packages/frontend/core/src/modules/ai-button/provider/ai-button.ts @@ -0,0 +1,9 @@ +import { createIdentifier } from '@toeverything/infra'; + +export interface AIButtonProvider { + presentAIButton: () => Promise; + dismissAIButton: () => Promise; +} + +export const AIButtonProvider = + createIdentifier('AIButtonProvider'); diff --git a/packages/frontend/core/src/modules/ai-button/services/ai-button.ts b/packages/frontend/core/src/modules/ai-button/services/ai-button.ts new file mode 100644 index 0000000000..807afe4563 --- /dev/null +++ b/packages/frontend/core/src/modules/ai-button/services/ai-button.ts @@ -0,0 +1,48 @@ +import { DebugLogger } from '@affine/debug'; +import { + effect, + exhaustMapWithTrailing, + fromPromise, + Service, +} from '@toeverything/infra'; +import { + catchError, + distinctUntilChanged, + EMPTY, + mergeMap, + throttleTime, +} from 'rxjs'; + +import type { AIButtonProvider } from '../provider/ai-button'; + +const logger = new DebugLogger('AIButtonService'); + +export class AIButtonService extends Service { + constructor(private readonly aiButtonProvider?: AIButtonProvider) { + super(); + } + + presentAIButton = effect( + distinctUntilChanged(), + throttleTime(1000), // throttle time to avoid frequent calls + exhaustMapWithTrailing((present: boolean) => { + return fromPromise(async () => { + if (!this.aiButtonProvider) { + return; + } + if (present) { + await this.aiButtonProvider.presentAIButton(); + } else { + await this.aiButtonProvider.dismissAIButton(); + } + return; + }).pipe( + mergeMap(() => EMPTY), + catchError(err => { + logger.error('presentAIButton error', err); + return EMPTY; + }) + ); + }) + ); +} diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index 26b7181de5..aa24b8d667 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -1,6 +1,7 @@ import { configureQuotaModule } from '@affine/core/modules/quota'; import { configureInfraModules, type Framework } from '@toeverything/infra'; +import { configureAIButtonModule } from './ai-button'; import { configureAppSidebarModule } from './app-sidebar'; import { configureCloudModule } from './cloud'; import { configureCollectionModule } from './collection'; @@ -67,4 +68,5 @@ export function configureCommonModules(framework: Framework) { configureDialogModule(framework); configureDocInfoModule(framework); configureOpenInApp(framework); + configureAIButtonModule(framework); }