mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00:00
feat(core): support tag search for ai chat (#10965)
Close [BS-2785](https://linear.app/affine-design/issue/BS-2785). Close [BS-2786](https://linear.app/affine-design/issue/BS-2786). 
This commit is contained in:
@@ -3,9 +3,9 @@ import { type Framework } from '@toeverything/infra';
|
||||
import { WorkspaceDialogService } from '../dialogs';
|
||||
import { DocsService } from '../doc';
|
||||
import { DocDisplayMetaService } from '../doc-display-meta';
|
||||
import { DocSearchMenuService } from '../doc-search-menu/services';
|
||||
import { EditorSettingService } from '../editor-setting';
|
||||
import { JournalService } from '../journal';
|
||||
import { SearchMenuService } from '../search-menu/services';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { AtMenuConfigService } from './services';
|
||||
|
||||
@@ -18,6 +18,6 @@ export function configAtMenuConfigModule(framework: Framework) {
|
||||
WorkspaceDialogService,
|
||||
EditorSettingService,
|
||||
DocsService,
|
||||
DocSearchMenuService,
|
||||
SearchMenuService,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import { html } from 'lit';
|
||||
import type { WorkspaceDialogService } from '../../dialogs';
|
||||
import type { DocsService } from '../../doc';
|
||||
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
||||
import type { DocSearchMenuService } from '../../doc-search-menu/services';
|
||||
import type { EditorSettingService } from '../../editor-setting';
|
||||
import { type JournalService, suggestJournalDate } from '../../journal';
|
||||
import type { SearchMenuService } from '../../search-menu/services';
|
||||
|
||||
function resolveSignal<T>(data: T | Signal<T>): T {
|
||||
return data instanceof Signal ? data.value : data;
|
||||
@@ -45,7 +45,7 @@ export class AtMenuConfigService extends Service {
|
||||
private readonly dialogService: WorkspaceDialogService,
|
||||
private readonly editorSettingService: EditorSettingService,
|
||||
private readonly docsService: DocsService,
|
||||
private readonly docsSearchMenuService: DocSearchMenuService
|
||||
private readonly searchMenuService: SearchMenuService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -292,7 +292,7 @@ export class AtMenuConfigService extends Service {
|
||||
track.doc.editor.atMenu.linkDoc();
|
||||
this.insertDoc(inlineEditor, meta.id);
|
||||
};
|
||||
const result = this.docsSearchMenuService.getDocMenuGroup(
|
||||
const result = this.searchMenuService.getDocMenuGroup(
|
||||
query,
|
||||
action,
|
||||
abortSignal
|
||||
|
||||
@@ -17,7 +17,6 @@ import { configureDocModule } from './doc';
|
||||
import { configureDocDisplayMetaModule } from './doc-display-meta';
|
||||
import { configureDocInfoModule } from './doc-info';
|
||||
import { configureDocLinksModule } from './doc-link';
|
||||
import { configDocSearchMenuModule } from './doc-search-menu';
|
||||
import { configureDocsSearchModule } from './docs-search';
|
||||
import { configureEditorModule } from './editor';
|
||||
import { configureEditorSettingModule } from './editor-setting';
|
||||
@@ -39,6 +38,7 @@ import { configurePDFModule } from './pdf';
|
||||
import { configurePeekViewModule } from './peek-view';
|
||||
import { configurePermissionsModule } from './permissions';
|
||||
import { configureQuickSearchModule } from './quicksearch';
|
||||
import { configSearchMenuModule } from './search-menu';
|
||||
import { configureShareDocsModule } from './share-doc';
|
||||
import { configureShareSettingModule } from './share-setting';
|
||||
import {
|
||||
@@ -96,7 +96,7 @@ export function configureCommonModules(framework: Framework) {
|
||||
configureDocInfoModule(framework);
|
||||
configureOpenInApp(framework);
|
||||
configAtMenuConfigModule(framework);
|
||||
configDocSearchMenuModule(framework);
|
||||
configSearchMenuModule(framework);
|
||||
configureDndModule(framework);
|
||||
configureCommonGlobalStorageImpls(framework);
|
||||
configureAINetworkSearchModule(framework);
|
||||
|
||||
@@ -3,16 +3,18 @@ import { type Framework } from '@toeverything/infra';
|
||||
import { DocDisplayMetaService } from '../doc-display-meta';
|
||||
import { DocsSearchService } from '../docs-search';
|
||||
import { RecentDocsService } from '../quicksearch';
|
||||
import { TagService } from '../tag';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { DocSearchMenuService } from './services';
|
||||
import { SearchMenuService } from './services';
|
||||
|
||||
export function configDocSearchMenuModule(framework: Framework) {
|
||||
export function configSearchMenuModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(DocSearchMenuService, [
|
||||
.service(SearchMenuService, [
|
||||
WorkspaceService,
|
||||
DocDisplayMetaService,
|
||||
RecentDocsService,
|
||||
DocsSearchService,
|
||||
TagService,
|
||||
]);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TagMeta } from '@affine/core/components/page-list';
|
||||
import { fuzzyMatch } from '@affine/core/utils/fuzzy-match';
|
||||
import { I18n } from '@affine/i18n';
|
||||
import type {
|
||||
@@ -9,13 +10,16 @@ import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import Fuse from 'fuse.js';
|
||||
import { html } from 'lit';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import { map, takeWhile } from 'rxjs';
|
||||
|
||||
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
import type { RecentDocsService } from '../../quicksearch';
|
||||
import { type RecentDocsService } from '../../quicksearch';
|
||||
import { highlighter } from '../../quicksearch/utils/highlighter';
|
||||
import type { TagService } from '../../tag';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
const MAX_DOCS = 3;
|
||||
@@ -26,12 +30,15 @@ type DocMetaWithHighlights = DocMeta & {
|
||||
|
||||
export type SearchDocMenuAction = (meta: DocMeta) => Promise<void> | void;
|
||||
|
||||
export class DocSearchMenuService extends Service {
|
||||
export type SearchTagMenuAction = (tagId: TagMeta) => Promise<void> | void;
|
||||
|
||||
export class SearchMenuService extends Service {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly docDisplayMetaService: DocDisplayMetaService,
|
||||
private readonly recentDocsService: RecentDocsService,
|
||||
private readonly docsSearch: DocsSearchService
|
||||
private readonly docsSearch: DocsSearchService,
|
||||
private readonly tagService: TagService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -210,4 +217,81 @@ export class DocSearchMenuService extends Service {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getTagMenuGroup(
|
||||
query: string,
|
||||
action: SearchTagMenuAction,
|
||||
_abortSignal: AbortSignal
|
||||
): LinkedMenuGroup {
|
||||
const tags: TagMeta[] = this.tagService.tagList.tagMetas$.value;
|
||||
|
||||
if (query.trim().length === 0) {
|
||||
return {
|
||||
name: I18n.t('com.affine.editor.at-menu.tags', {
|
||||
query,
|
||||
}),
|
||||
items: tags.map(tag => this.toTagMenuItem(tag, action)),
|
||||
};
|
||||
}
|
||||
|
||||
const fuse = new Fuse(tags, {
|
||||
keys: ['title'],
|
||||
includeMatches: true,
|
||||
includeScore: true,
|
||||
ignoreLocation: true,
|
||||
threshold: 0.0,
|
||||
});
|
||||
|
||||
const result = fuse.search(query);
|
||||
|
||||
return {
|
||||
name: I18n.t('com.affine.editor.at-menu.link-to-doc', {
|
||||
query,
|
||||
}),
|
||||
items: result.map(item => {
|
||||
const normalizedRange = ([start, end]: [number, number]) =>
|
||||
[
|
||||
start,
|
||||
end + 1 /* in fuse, the `end` is different from the `substring` */,
|
||||
] as [number, number];
|
||||
const titleMatches = item.matches
|
||||
?.filter(match => match.key === 'title')
|
||||
.flatMap(match => match.indices.map(normalizedRange));
|
||||
const hTitle = highlighter(
|
||||
item.item.title,
|
||||
`<span style="color: ${cssVarV2('text/emphasis')}">`,
|
||||
'</span>',
|
||||
titleMatches ?? []
|
||||
);
|
||||
return this.toTagMenuItem(
|
||||
{
|
||||
...item.item,
|
||||
title: hTitle ?? item.item.title,
|
||||
},
|
||||
action
|
||||
);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private toTagMenuItem(
|
||||
tag: TagMeta,
|
||||
action: SearchTagMenuAction
|
||||
): LinkedMenuItem {
|
||||
const tagIcon = html`
|
||||
<div style="display: flex; align-items: center; justify-content: center;">
|
||||
<div
|
||||
style="border-radius: 50%; height: 8px; width: 8px; margin: 4px; background-color: ${tag.color};"
|
||||
></div>
|
||||
</div>
|
||||
`;
|
||||
return {
|
||||
key: tag.id,
|
||||
name: html`${unsafeHTML(tag.title)}`,
|
||||
icon: tagIcon,
|
||||
action: async () => {
|
||||
await action(tag);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user