feat(android): integrate web api & native AI chat button (#10239)

This commit is contained in:
Aki Chang
2025-02-18 16:23:25 +08:00
committed by GitHub
parent 892fd16f52
commit 3f4b7ec51e
15 changed files with 302 additions and 23 deletions
+124 -4
View File
@@ -4,7 +4,14 @@ import { configureMobileModules } from '@affine/core/mobile/modules';
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules';
import { AuthService, DefaultServerService } from '@affine/core/modules/cloud';
import { AIButtonProvider } from '@affine/core/modules/ai-button';
import {
AuthService,
DefaultServerService,
ServersService,
} from '@affine/core/modules/cloud';
import { DocsService } from '@affine/core/modules/doc';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { I18nProvider } from '@affine/core/modules/i18n';
import { LifecycleService } from '@affine/core/modules/lifecycle';
import {
@@ -14,8 +21,20 @@ import {
import { PopupWindowProvider } from '@affine/core/modules/url';
import { ClientSchemeProvider } from '@affine/core/modules/url/providers/client-schema';
import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench';
import { WorkspacesService } from '@affine/core/modules/workspace';
import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine';
import { I18n } from '@affine/i18n';
import { StoreManagerClient } from '@affine/nbstore/worker/client';
import {
defaultBlockMarkdownAdapterMatchers,
docLinkBaseURLMiddleware,
InlineDeltaToMarkdownAdapterExtensions,
MarkdownAdapter,
MarkdownInlineToDeltaAdapterExtensions,
titleMiddleware,
} from '@blocksuite/affine/blocks';
import { Container } from '@blocksuite/affine/global/di';
import { Transformer } from '@blocksuite/affine/store';
import { App as CapacitorApp } from '@capacitor/app';
import { Keyboard } from '@capacitor/keyboard';
import { StatusBar, Style } from '@capacitor/status-bar';
@@ -27,6 +46,9 @@ import { useTheme } from 'next-themes';
import { Suspense, useEffect } from 'react';
import { RouterProvider } from 'react-router-dom';
import { AffineTheme } from './plugins/affine-theme';
import { AIButton } from './plugins/ai-button';
const storeManagerClient = new StoreManagerClient(
new OpClient(
new Worker(
@@ -88,6 +110,102 @@ framework.impl(VirtualKeyboardProvider, {
},
});
framework.impl(AIButtonProvider, {
presentAIButton: () => {
return AIButton.present();
},
dismissAIButton: () => {
return AIButton.dismiss();
},
});
// ------ some apis for native ------
(window as any).getCurrentServerBaseUrl = () => {
const globalContextService = frameworkProvider.get(GlobalContextService);
const currentServerId = globalContextService.globalContext.serverId.get();
const serversService = frameworkProvider.get(ServersService);
const defaultServerService = frameworkProvider.get(DefaultServerService);
const currentServer =
(currentServerId ? serversService.server$(currentServerId).value : null) ??
defaultServerService.server;
return currentServer.baseUrl;
};
(window as any).getCurrentI18nLocale = () => {
return I18n.language;
};
(window as any).getCurrentWorkspaceId = () => {
const globalContextService = frameworkProvider.get(GlobalContextService);
return globalContextService.globalContext.workspaceId.get();
};
(window as any).getCurrentDocId = () => {
const globalContextService = frameworkProvider.get(GlobalContextService);
return globalContextService.globalContext.docId.get();
};
(window as any).getCurrentDocContentInMarkdown = async () => {
const globalContextService = frameworkProvider.get(GlobalContextService);
const currentWorkspaceId =
globalContextService.globalContext.workspaceId.get();
const currentDocId = globalContextService.globalContext.docId.get();
const workspacesService = frameworkProvider.get(WorkspacesService);
const workspaceRef = currentWorkspaceId
? workspacesService.openByWorkspaceId(currentWorkspaceId)
: null;
if (!workspaceRef) {
return;
}
const { workspace, dispose: disposeWorkspace } = workspaceRef;
const docsService = workspace.scope.get(DocsService);
const docRef = currentDocId ? docsService.open(currentDocId) : null;
if (!docRef) {
return;
}
const { doc, release: disposeDoc } = docRef;
try {
const blockSuiteDoc = doc.blockSuiteDoc;
const transformer = new Transformer({
schema: blockSuiteDoc.workspace.schema,
blobCRUD: blockSuiteDoc.workspace.blobSync,
docCRUD: {
create: (id: string) => blockSuiteDoc.workspace.createDoc({ id }),
get: (id: string) => blockSuiteDoc.workspace.getDoc(id),
delete: (id: string) => blockSuiteDoc.workspace.removeDoc(id),
},
middlewares: [
docLinkBaseURLMiddleware(blockSuiteDoc.workspace.id),
titleMiddleware(blockSuiteDoc.workspace.meta.docMetas),
],
});
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
const container = new Container();
[
...MarkdownInlineToDeltaAdapterExtensions,
...defaultBlockMarkdownAdapterMatchers,
...InlineDeltaToMarkdownAdapterExtensions,
].forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
const adapter = new MarkdownAdapter(transformer, provider);
if (!snapshot) {
return;
}
const markdownResult = await adapter.fromDocSnapshot({
snapshot,
assets: transformer.assetsManager,
});
return markdownResult.file;
} finally {
disposeDoc();
disposeWorkspace();
}
};
// setup application lifecycle events, and emit application start event
window.addEventListener('focus', () => {
frameworkProvider.get(LifecycleService).applicationFocus();
@@ -130,7 +248,7 @@ CapacitorApp.addListener('appUrlOpen', ({ url }) => {
console.error(e);
});
const EdgeToEdgeCompatibilityProvider = () => {
const ThemeProvider = () => {
const { resolvedTheme } = useTheme();
useEffect(() => {
@@ -145,8 +263,10 @@ const EdgeToEdgeCompatibilityProvider = () => {
EdgeToEdge.setBackgroundColor({
color: resolvedTheme === 'dark' ? '#000000' : '#F5F5F5',
}).catch(console.error);
AffineTheme.onThemeChanged({
darkMode: resolvedTheme === 'dark',
}).catch(console.error);
}, [resolvedTheme]);
return null;
};
@@ -156,7 +276,7 @@ export function App() {
<FrameworkRoot framework={frameworkProvider}>
<I18nProvider>
<AffineContext store={getCurrentStore()}>
<EdgeToEdgeCompatibilityProvider />
<ThemeProvider />
<RouterProvider
fallbackElement={<AppFallback />}
router={router}
@@ -0,0 +1,3 @@
export interface AffineThemePlugin {
onThemeChanged(options: { darkMode: boolean }): Promise<void>;
}
@@ -0,0 +1,8 @@
import { registerPlugin } from '@capacitor/core';
import type { AffineThemePlugin } from './definitions';
const AffineTheme = registerPlugin<AffineThemePlugin>('AffineTheme');
export * from './definitions';
export { AffineTheme };
@@ -0,0 +1,4 @@
export interface AIButtonPlugin {
present(): Promise<void>;
dismiss(): Promise<void>;
}
@@ -0,0 +1,8 @@
import { registerPlugin } from '@capacitor/core';
import type { AIButtonPlugin } from './definitions';
const AIButton = registerPlugin<AIButtonPlugin>('AIButton');
export * from './definitions';
export { AIButton };