Skip to content

Commit 825c630

Browse files
committed
feat(core): workspace embedding settings (#11801)
1 parent e69fa05 commit 825c630

File tree

25 files changed

+1263
-86
lines changed

25 files changed

+1263
-86
lines changed

.github/workflows/build-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ jobs:
10811081
- 'packages/backend/server/src/plugins/copilot/**'
10821082
- 'packages/backend/server/tests/copilot.*'
10831083
- 'packages/frontend/core/src/blocksuite/ai/**'
1084+
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
10841085
- 'tests/affine-cloud-copilot/**'
10851086
10861087
- name: Setup Node.js
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import {
2+
FileIconAepIcon,
3+
FileIconAiIcon,
4+
FileIconAviIcon,
5+
FileIconCssIcon,
6+
FileIconCsvIcon,
7+
FileIconDmgIcon,
8+
FileIconDocIcon,
9+
FileIconDocxIcon,
10+
FileIconEpsIcon,
11+
FileIconExeIcon,
12+
FileIconFigIcon,
13+
FileIconGifIcon,
14+
FileIconHtmlIcon,
15+
FileIconInddIcon,
16+
FileIconJavaIcon,
17+
FileIconJpegIcon,
18+
FileIconJsIcon,
19+
FileIconJsonIcon,
20+
FileIconMkvIcon,
21+
FileIconMp3Icon,
22+
FileIconMp4Icon,
23+
FileIconMpegIcon,
24+
FileIconNoneIcon,
25+
FileIconPdfIcon,
26+
FileIconPngIcon,
27+
FileIconPptIcon,
28+
FileIconPptxIcon,
29+
FileIconPsdIcon,
30+
FileIconRarIcon,
31+
FileIconRssIcon,
32+
FileIconSqlIcon,
33+
FileIconSvgIcon,
34+
FileIconTiffIcon,
35+
FileIconTxtIcon,
36+
FileIconWavIcon,
37+
FileIconWebpIcon,
38+
FileIconXlsIcon,
39+
FileIconXlsxIcon,
40+
FileIconXmlIcon,
41+
FileIconZipIcon,
42+
ImageIcon,
43+
} from '@blocksuite/icons/rc';
44+
45+
export function getAttachmentFileIconRC(filetype: string) {
46+
switch (filetype) {
47+
case 'img':
48+
return ImageIcon;
49+
case 'image/jpeg':
50+
case 'jpg':
51+
case 'jpeg':
52+
return FileIconJpegIcon;
53+
case 'image/png':
54+
case 'png':
55+
return FileIconPngIcon;
56+
case 'image/webp':
57+
case 'webp':
58+
return FileIconWebpIcon;
59+
case 'image/tiff':
60+
case 'tiff':
61+
return FileIconTiffIcon;
62+
case 'image/gif':
63+
case 'gif':
64+
return FileIconGifIcon;
65+
case 'image/svg':
66+
case 'svg':
67+
return FileIconSvgIcon;
68+
case 'image/eps':
69+
case 'eps':
70+
return FileIconEpsIcon;
71+
case 'application/pdf':
72+
case 'pdf':
73+
return FileIconPdfIcon;
74+
case 'application/msword':
75+
case 'application/x-iwork-pages-sffpages':
76+
case 'doc':
77+
return FileIconDocIcon;
78+
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
79+
case 'docx':
80+
return FileIconDocxIcon;
81+
case 'text/plain':
82+
case 'txt':
83+
return FileIconTxtIcon;
84+
case 'csv':
85+
return FileIconCsvIcon;
86+
case 'application/vnd.ms-excel':
87+
case 'xls':
88+
return FileIconXlsIcon;
89+
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
90+
case 'application/x-iwork-numbers-sffnumbers':
91+
case 'xlsx':
92+
return FileIconXlsxIcon;
93+
case 'application/vnd.ms-powerpoint':
94+
case 'application/x-iwork-keynote-sffkeynote':
95+
case 'ppt':
96+
return FileIconPptIcon;
97+
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
98+
case 'pptx':
99+
return FileIconPptxIcon;
100+
case 'application/illustrator':
101+
case 'fig':
102+
return FileIconFigIcon;
103+
case 'application/postscript':
104+
case 'ai':
105+
return FileIconAiIcon;
106+
case 'application/vnd.adobe.photoshop':
107+
case 'psd':
108+
return FileIconPsdIcon;
109+
case 'application/vnd.adobe.indesign':
110+
case 'indd':
111+
return FileIconInddIcon;
112+
case 'application/vnd.adobe.afterfx':
113+
case 'aep':
114+
return FileIconAepIcon;
115+
case 'audio/mpeg':
116+
case 'audio/mp3':
117+
case 'mp3':
118+
return FileIconMp3Icon;
119+
case 'audio/wav':
120+
case 'wav':
121+
return FileIconWavIcon;
122+
case 'video/mpeg':
123+
case 'mpeg':
124+
return FileIconMpegIcon;
125+
case 'video/mp4':
126+
case 'mp4':
127+
return FileIconMp4Icon;
128+
case 'video/avi':
129+
case 'avi':
130+
return FileIconAviIcon;
131+
case 'video/mkv':
132+
case 'mkv':
133+
return FileIconMkvIcon;
134+
case 'text/html':
135+
case 'html':
136+
return FileIconHtmlIcon;
137+
case 'text/css':
138+
case 'css':
139+
return FileIconCssIcon;
140+
case 'application/rss+xml':
141+
case 'rss':
142+
return FileIconRssIcon;
143+
case 'application/sql':
144+
case 'sql':
145+
return FileIconSqlIcon;
146+
case 'application/javascript':
147+
case 'js':
148+
return FileIconJsIcon;
149+
case 'application/json':
150+
case 'json':
151+
return FileIconJsonIcon;
152+
case 'application/java':
153+
case 'java':
154+
return FileIconJavaIcon;
155+
case 'application/xml':
156+
case 'xml':
157+
return FileIconXmlIcon;
158+
case 'application/x-msdos-program':
159+
case 'exe':
160+
return FileIconExeIcon;
161+
case 'application/x-apple-diskimage':
162+
case 'dmg':
163+
return FileIconDmgIcon;
164+
case 'application/zip':
165+
case 'zip':
166+
return FileIconZipIcon;
167+
case 'application/x-rar-compressed':
168+
case 'rar':
169+
return FileIconRarIcon;
170+
default:
171+
return FileIconNoneIcon;
172+
}
173+
}

blocksuite/affine/components/src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './ai.js';
22
export * from './file-icons.js';
3+
export * from './file-icons-rc';
34
export * from './import-export.js';
45
export * from './list.js';
56
export * from './loading.js';

packages/frontend/component/src/components/setting-components/setting-row.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { settingRow } from './share.css';
55

66
export type SettingRowProps = PropsWithChildren<{
77
name: ReactNode;
8-
desc: ReactNode;
8+
desc?: ReactNode;
99
style?: CSSProperties;
1010
onClick?: () => void;
1111
spreadCol?: boolean;
@@ -41,7 +41,7 @@ export const SettingRow = ({
4141
>
4242
<div className="left-col">
4343
<div className="name">{name}</div>
44-
<div className="desc">{desc}</div>
44+
{desc && <div className="desc">{desc}</div>}
4545
</div>
4646
{spreadCol ? <div className="right-col">{children}</div> : children}
4747
</div>

packages/frontend/component/src/components/setting-components/wrapper.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ interface SettingWrapperProps {
77
id?: string;
88
title?: ReactNode;
99
disabled?: boolean;
10+
testId?: string;
1011
}
1112

1213
export const SettingWrapper = ({
1314
id,
1415
title,
1516
children,
1617
disabled,
18+
testId,
1719
}: PropsWithChildren<SettingWrapperProps>) => {
1820
return (
19-
<div id={id} className={clsx(wrapper, disabled && wrapperDisabled)}>
21+
<div
22+
id={id}
23+
className={clsx(wrapper, disabled && wrapperDisabled)}
24+
data-testid={testId}
25+
>
2026
{title ? <div className="title">{title}</div> : null}
2127
{children}
2228
</div>

packages/frontend/core/src/blocksuite/ai/components/ai-chat-input/ai-chat-input.ts

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,8 @@ import { ChatAbortIcon, ChatSendIcon } from '../../_common/icons';
2020
import { type AIError, AIProvider } from '../../provider';
2121
import { reportResponse } from '../../utils/action-reporter';
2222
import { readBlobAsURL } from '../../utils/image';
23-
import type {
24-
ChatChip,
25-
DocDisplayConfig,
26-
FileChip,
27-
} from '../ai-chat-chips/type';
28-
import {
29-
isCollectionChip,
30-
isDocChip,
31-
isFileChip,
32-
isTagChip,
33-
} from '../ai-chat-chips/utils';
23+
import type { ChatChip, DocDisplayConfig } from '../ai-chat-chips/type';
24+
import { isDocChip } from '../ai-chat-chips/utils';
3425
import type { ChatMessage } from '../ai-chat-messages';
3526
import { MAX_IMAGE_COUNT } from './const';
3627
import type {
@@ -588,7 +579,6 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
588579

589580
const sessionId = await this.createSessionId();
590581
let contexts = await this._getMatchedContexts(userInput);
591-
contexts = this._filterContexts(contexts);
592582
if (abortController.signal.aborted) {
593583
return;
594584
}
@@ -673,9 +663,7 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
673663

674664
private async _getMatchedContexts(userInput: string) {
675665
const contextId = await this.getContextId();
676-
if (!contextId) {
677-
return { files: [], docs: [] };
678-
}
666+
const workspaceId = this.host.store.workspace.id;
679667

680668
const docContexts = new Map<
681669
string,
@@ -687,7 +675,11 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
687675
>();
688676

689677
const { files: matchedFiles = [], docs: matchedDocs = [] } =
690-
(await AIProvider.context?.matchContext(userInput, contextId)) ?? {};
678+
(await AIProvider.context?.matchContext(
679+
userInput,
680+
contextId,
681+
workspaceId
682+
)) ?? {};
691683

692684
matchedDocs.forEach(doc => {
693685
docContexts.set(doc.docId, {
@@ -701,17 +693,12 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
701693
if (context) {
702694
context.fileContent += `\n${file.content}`;
703695
} else {
704-
const fileChip = this.chips.find(
705-
chip => isFileChip(chip) && chip.fileId === file.fileId
706-
) as FileChip | undefined;
707-
if (fileChip && fileChip.blobId) {
708-
fileContexts.set(file.fileId, {
709-
blobId: fileChip.blobId,
710-
fileName: fileChip.file.name,
711-
fileType: fileChip.file.type,
712-
fileContent: file.content,
713-
});
714-
}
696+
fileContexts.set(file.fileId, {
697+
blobId: file.blobId,
698+
fileName: file.name,
699+
fileType: file.mimeType,
700+
fileContent: file.content,
701+
});
715702
}
716703
});
717704

@@ -753,42 +740,6 @@ export class AIChatInput extends SignalWatcher(WithDisposable(LitElement)) {
753740
files: Array.from(fileContexts.values()),
754741
};
755742
}
756-
757-
// TODO: remove this function after workspace embedding is ready
758-
private _filterContexts(contexts: {
759-
docs: BlockSuitePresets.AIDocContextOption[];
760-
files: BlockSuitePresets.AIFileContextOption[];
761-
}) {
762-
const docIds = this.chips.reduce((acc, chip) => {
763-
if (isDocChip(chip)) {
764-
acc.push(chip.docId);
765-
}
766-
if (isTagChip(chip)) {
767-
const docIds = this.docDisplayConfig.getTagPageIds(chip.tagId);
768-
acc.push(...docIds);
769-
}
770-
if (isCollectionChip(chip)) {
771-
const docIds = this.docDisplayConfig.getCollectionPageIds(
772-
chip.collectionId
773-
);
774-
acc.push(...docIds);
775-
}
776-
return acc;
777-
}, [] as string[]);
778-
779-
const fileIds = this.chips.reduce((acc, chip) => {
780-
if (isFileChip(chip) && chip.blobId) {
781-
acc.push(chip.blobId);
782-
}
783-
return acc;
784-
}, [] as string[]);
785-
786-
const { docs, files } = contexts;
787-
return {
788-
docs: docs.filter(doc => docIds.includes(doc.docId)),
789-
files: files.filter(file => fileIds.includes(file.blobId)),
790-
};
791-
}
792743
}
793744

794745
declare global {

packages/frontend/core/src/components/page-list/selector/selector-layout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ export const SelectorLayout = ({
4444
);
4545

4646
return (
47-
<div className={styles.root}>
47+
<div className={styles.root} data-testid="doc-selector-layout">
4848
<header className={styles.header}>
4949
<RowInput
50+
data-testid="doc-selector-search-input"
5051
className={styles.search}
5152
placeholder={searchPlaceholder}
5253
onChange={onSearchChange}
@@ -73,10 +74,15 @@ export const SelectorLayout = ({
7374
<div className={styles.footerAction}>
7475
{actions ?? (
7576
<>
76-
<Button onClick={onCancel} className={styles.actionButton}>
77+
<Button
78+
data-testid="doc-selector-cancel-button"
79+
onClick={onCancel}
80+
className={styles.actionButton}
81+
>
7782
{t['Cancel']()}
7883
</Button>
7984
<Button
85+
data-testid="doc-selector-confirm-button"
8086
onClick={onConfirm}
8187
className={styles.actionButton}
8288
variant="primary"

0 commit comments

Comments
 (0)