diff --git a/packages/frontend/component/src/components/setting-components/wrapper.tsx b/packages/frontend/component/src/components/setting-components/wrapper.tsx index cfdd5bd36..4aec5c3e3 100644 --- a/packages/frontend/component/src/components/setting-components/wrapper.tsx +++ b/packages/frontend/component/src/components/setting-components/wrapper.tsx @@ -22,6 +22,7 @@ export const SettingWrapper = ({ id={id} className={clsx(wrapper, disabled && wrapperDisabled)} data-testid={testId} + aria-disabled={disabled} > {title ?
{title}
: null} {children} diff --git a/packages/frontend/core/src/modules/workspace-indexer-embedding/view/attachments.tsx b/packages/frontend/core/src/modules/workspace-indexer-embedding/view/attachments.tsx index b8f6e2df9..d1103e5e6 100644 --- a/packages/frontend/core/src/modules/workspace-indexer-embedding/view/attachments.tsx +++ b/packages/frontend/core/src/modules/workspace-indexer-embedding/view/attachments.tsx @@ -33,11 +33,13 @@ interface AttachmentsProps { totalCount: number; onPageChange: (offset: number) => void; onDelete: (id: string) => void; + disabled: boolean; } interface AttachmentItemProps { attachment: AttachmentFile; onDelete: (id: string) => void; + disabled: boolean; } const UploadingItem: React.FC<{ attachment: UploadingAttachmentFile }> = ({ @@ -88,6 +90,7 @@ const PersistedItem: React.FC<{ attachment: PersistedAttachmentFile }> = ({ const AttachmentItem: React.FC = ({ attachment, onDelete, + disabled, }) => { const t = useI18n(); const { openConfirmModal } = useConfirmModal(); @@ -131,20 +134,22 @@ const AttachmentItem: React.FC = ({ ) : isPersistedAttachment(attachment) ? ( ) : null} -
- - - -
+ {!disabled && ( +
+ + + +
+ )} ); }; @@ -154,6 +159,7 @@ export const Attachments: React.FC = ({ totalCount, onDelete, onPageChange, + disabled, }) => { const handlePageChange = useCallback( (offset: number) => { @@ -172,6 +178,7 @@ export const Attachments: React.FC = ({ key={getAttachmentId(attachment)} attachment={attachment} onDelete={onDelete} + disabled={disabled} /> ))} = () => { +const EmbeddingLocal: React.FC<{}> = () => { + return ; +}; + +const EmbeddingCloud: React.FC<{ disabled: boolean }> = ({ disabled }) => { const t = useI18n(); const embeddingService = useService(EmbeddingService); const workspaceDialogService = useService(WorkspaceDialogService); @@ -171,96 +179,135 @@ export const EmbeddingSettings: React.FC = () => { ]); return ( - <> - + - - - - - - + - - - - - - - - - - {attachments.length > 0 && ( - + { + <> + - )} - + + + } + + + - + + - {ignoredDocs.length > 0 && ( - - )} - + {attachments.length > 0 && ( + + )} + + + + + + {ignoredDocs.length > 0 && ( + + )} + + ); +}; + +export const EmbeddingSettings: React.FC = () => { + const workspaceService = useService(WorkspaceService); + const serverService = useService(ServerService); + const isLocal = workspaceService.workspace.flavour === 'local'; + const serverConfig = useLiveData(serverService.server.config$); + const isEmbeddingEnabled = serverConfig?.features.includes( + ServerFeature.CopilotEmbedding + ); + + const t = useI18n(); + + return ( + <> + + + + + {isLocal ? ( + + ) : ( + + )} ); }; diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index e1543bdee..c93839740 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -6285,6 +6285,10 @@ export function useAFFiNEI18N(): { * `Embedding allows AI to retrieve your content. If the indexer uses local settings, it may affect some of the results of the Embedding.` */ ["com.affine.settings.workspace.indexer-embedding.embedding.description"](): string; + /** + * `Only the workspace owner can enable Workspace Embedding.` + */ + ["com.affine.settings.workspace.indexer-embedding.embedding.disabled-tooltip"](): string; /** * `Select doc` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 18ff77036..4ac942a9d 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1575,6 +1575,7 @@ "com.affine.settings.workspace.indexer-embedding.description": "Manage AFFiNE indexing and AFFiNE AI Embedding for local content processing", "com.affine.settings.workspace.indexer-embedding.embedding.title": "Embedding", "com.affine.settings.workspace.indexer-embedding.embedding.description": "Embedding allows AI to retrieve your content. If the indexer uses local settings, it may affect some of the results of the Embedding.", + "com.affine.settings.workspace.indexer-embedding.embedding.disabled-tooltip": "Only the workspace owner can enable Workspace Embedding.", "com.affine.settings.workspace.indexer-embedding.embedding.select-doc": "Select doc", "com.affine.settings.workspace.indexer-embedding.embedding.upload-file": "Upload file", "com.affine.settings.workspace.indexer-embedding.embedding.switch.title": "Workspace Embedding", diff --git a/tests/affine-cloud-copilot/e2e/settings/embedding.spec.ts b/tests/affine-cloud-copilot/e2e/settings/embedding.spec.ts index ee13f8bfe..52db8a8b2 100644 --- a/tests/affine-cloud-copilot/e2e/settings/embedding.spec.ts +++ b/tests/affine-cloud-copilot/e2e/settings/embedding.spec.ts @@ -1,3 +1,4 @@ +import { createLocalWorkspace } from '@affine-test/kit/utils/workspace'; import { expect } from '@playwright/test'; import { test } from '../base/base-test'; @@ -43,6 +44,57 @@ test.describe('AISettings/Embedding', () => { await utils.settings.waitForWorkspaceEmbeddingSwitchToBe(page, true); }); + test('should show enable cloud panel if workspace is local', async ({ + loggedInPage: page, + utils, + }) => { + await utils.settings.closeSettingsPanel(page); + await createLocalWorkspace({ name: 'test' }, page); + await utils.settings.openSettingsPanel(page); + await expect( + page.getByTestId('publish-enable-affine-cloud-button') + ).toBeVisible(); + }); + + test('should disable embedding settings if the user is not workspace owner', async ({ + loggedInPage: page, + utils, + }) => { + // mock the features to be empty(without CopilotEmbedding) + await page.route('**/graphql', async (route, request) => { + const postData = request.postData(); + if (postData && postData.includes('serverConfig')) { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + data: { + serverConfig: { + version: '1.0.0', + baseUrl: 'http://localhost:8080', + name: 'AFFiNE', + features: [], + type: 'cloud', + initialized: true, + credentialsRequirement: null, + }, + }, + }), + }); + } else { + await route.continue(); + } + }); + + await page.reload(); + await utils.settings.openSettingsPanel(page); + + const wrapper = await page.getByTestId( + 'workspace-embedding-setting-wrapper' + ); + await expect(wrapper).toHaveAttribute('aria-disabled', 'true'); + }); + test('should show error message if enable workspace embedding failed', async ({ loggedInPage: page, utils, diff --git a/tests/affine-cloud-copilot/e2e/utils/settings-panel-utils.ts b/tests/affine-cloud-copilot/e2e/utils/settings-panel-utils.ts index ac46288ce..b59934028 100644 --- a/tests/affine-cloud-copilot/e2e/utils/settings-panel-utils.ts +++ b/tests/affine-cloud-copilot/e2e/utils/settings-panel-utils.ts @@ -7,7 +7,7 @@ export class SettingsPanelUtils { if (await page.getByTestId('workspace-setting:embedding').isHidden()) { await page.getByTestId('slider-bar-workspace-setting-button').click(); await page.getByTestId('workspace-setting:embedding').click(); - await page.getByTestId('workspace-embedding-setting-wrapper').waitFor({ + await page.getByTestId('workspace-embedding-setting-header').waitFor({ state: 'visible', }); }