mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-04 19:15:33 +08:00
feat(core): center peek doc in chat semantic/keyword search result (#13380)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the ability to preview documents directly from AI chat search results using a new document peek view. * Search result items in AI chat are now clickable, allowing for quick document previews without leaving the chat interface. * **Style** * Updated clickable item styles in search results for improved visual feedback and consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -4,6 +4,7 @@ import type {
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import type {
|
||||
@@ -125,6 +126,9 @@ export class ChatPanel extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
@state()
|
||||
accessor session: CopilotChatHistoryFragment | null | undefined;
|
||||
|
||||
@@ -421,6 +425,7 @@ export class ChatPanel extends SignalWatcher(
|
||||
.notificationService=${this.notificationService}
|
||||
.aiDraftService=${this.aiDraftService}
|
||||
.aiToolsConfigService=${this.aiToolsConfigService}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||
.onContextChange=${this.onContextChange}
|
||||
.width=${this.sidebarWidth}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
@@ -86,6 +87,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@@ -150,6 +154,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
.theme=${this.affineThemeService.appTheme.themeSignal}
|
||||
.independentMode=${this.independentMode}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></chat-content-stream-objects>`;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
import type { AIDraftState } from '@affine/core/modules/ai-button/services/ai-draft';
|
||||
import type { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type {
|
||||
ContextEmbedStatus,
|
||||
@@ -178,6 +179,9 @@ export class AIChatContent extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor width: Signal<number | undefined> | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
@state()
|
||||
accessor chatContextValue: ChatContextValue = DEFAULT_CHAT_CONTEXT_VALUE;
|
||||
|
||||
@@ -427,6 +431,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
.independentMode=${this.independentMode}
|
||||
.messages=${this.messages}
|
||||
.docDisplayService=${this.docDisplayConfig}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></ai-chat-messages>
|
||||
<ai-chat-composer
|
||||
|
||||
+5
@@ -1,4 +1,5 @@
|
||||
import type { AIToolsConfigService } from '@affine/core/modules/ai-button';
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import type { AppThemeService } from '@affine/core/modules/theme';
|
||||
import type { CopilotChatHistoryFragment } from '@affine/graphql';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
@@ -210,6 +211,9 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor aiToolsConfigService!: AIToolsConfigService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@@ -340,6 +344,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
.width=${this.width}
|
||||
.independentMode=${this.independentMode}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></chat-message-assistant>`;
|
||||
} else if (isChatAction(item) && this.host) {
|
||||
|
||||
+7
@@ -1,4 +1,5 @@
|
||||
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import type { ColorScheme } from '@blocksuite/affine/model';
|
||||
import {
|
||||
@@ -61,6 +62,9 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@@ -115,6 +119,7 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
return html`<doc-semantic-search-result
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.peekViewService=${this.peekViewService}
|
||||
></doc-semantic-search-result>`;
|
||||
case 'doc_keyword_search':
|
||||
return html`<doc-keyword-search-result
|
||||
@@ -201,12 +206,14 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></doc-semantic-search-result>`;
|
||||
case 'doc_keyword_search':
|
||||
return html`<doc-keyword-search-result
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.peekViewService=${this.peekViewService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></doc-keyword-search-result>`;
|
||||
case 'doc_read':
|
||||
|
||||
+12
@@ -1,3 +1,4 @@
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { PageIcon, SearchIcon } from '@blocksuite/icons/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
@@ -41,6 +42,9 @@ export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
renderToolCall() {
|
||||
return html`<tool-call-card
|
||||
.name=${`Searching workspace documents for "${this.data.args.query}"`}
|
||||
@@ -63,6 +67,14 @@ export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
${item.title}
|
||||
</span>`,
|
||||
icon: PageIcon(),
|
||||
onClick: () => {
|
||||
this.peekViewService.peekView
|
||||
.open({
|
||||
type: 'doc',
|
||||
docRef: { docId: item.docId },
|
||||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Failed to parse result', err);
|
||||
|
||||
+14
@@ -1,3 +1,4 @@
|
||||
import type { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { AiEmbeddingIcon, PageIcon } from '@blocksuite/icons/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
@@ -72,6 +73,9 @@ export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor peekViewService!: PeekViewService;
|
||||
|
||||
renderToolCall() {
|
||||
return html`<tool-call-card
|
||||
.name=${`Finding semantically related pages for "${this.data.args.query}"`}
|
||||
@@ -97,6 +101,16 @@ export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
>
|
||||
${this.docDisplayService.getTitle(result.docId)}
|
||||
</span>`,
|
||||
onClick: () => {
|
||||
this.peekViewService.peekView
|
||||
.open({
|
||||
type: 'doc',
|
||||
docRef: {
|
||||
docId: result.docId,
|
||||
},
|
||||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
}))
|
||||
.filter(Boolean)}
|
||||
></tool-result-card>`;
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ToolResult {
|
||||
icon?: string | TemplateResult<1>;
|
||||
content?: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export class ToolResultCard extends SignalWatcher(
|
||||
@@ -95,7 +96,8 @@ export class ToolResultCard extends SignalWatcher(
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.result-item[href] {
|
||||
.result-item[href],
|
||||
.result-item[data-clickable] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -154,7 +156,9 @@ export class ToolResultCard extends SignalWatcher(
|
||||
}
|
||||
|
||||
.result-item[href]:hover .result-title,
|
||||
.result-item[href]:hover .result-content {
|
||||
.result-item[href]:hover .result-content,
|
||||
.result-item[data-clickable]:hover .result-title,
|
||||
.result-item[data-clickable]:hover .result-content {
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
}
|
||||
|
||||
@@ -184,6 +188,27 @@ export class ToolResultCard extends SignalWatcher(
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
}
|
||||
|
||||
.result-icon,
|
||||
.footer-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
background-color: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
|
||||
img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@@ -244,11 +269,13 @@ export class ToolResultCard extends SignalWatcher(
|
||||
result => html`
|
||||
<a
|
||||
class="result-item"
|
||||
data-clickable=${!!result.onClick}
|
||||
href=${ifDefined(result.href)}
|
||||
target=${ifDefined(result.href ? '_blank' : undefined)}
|
||||
rel=${ifDefined(
|
||||
result.href ? 'noopener noreferrer' : undefined
|
||||
)}
|
||||
@click=${result.onClick}
|
||||
>
|
||||
<div class="result-header">
|
||||
<div class="result-title">${result.title}</div>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '@affine/core/modules/cloud';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
import {
|
||||
ViewBody,
|
||||
@@ -220,6 +221,7 @@ export const Component = () => {
|
||||
content.affineWorkspaceDialogService = framework.get(
|
||||
WorkspaceDialogService
|
||||
);
|
||||
content.peekViewService = framework.get(PeekViewService);
|
||||
content.affineThemeService = framework.get(AppThemeService);
|
||||
content.notificationService = new NotificationServiceImpl(
|
||||
confirmModal.closeConfirmModal,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@affine/core/modules/ai-button';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||
@@ -95,6 +96,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
|
||||
chatPanelRef.current.affineWorkbenchService =
|
||||
framework.get(WorkbenchService);
|
||||
chatPanelRef.current.affineThemeService = framework.get(AppThemeService);
|
||||
chatPanelRef.current.peekViewService = framework.get(PeekViewService);
|
||||
chatPanelRef.current.notificationService = new NotificationServiceImpl(
|
||||
confirmModal.closeConfirmModal,
|
||||
confirmModal.openConfirmModal
|
||||
|
||||
Reference in New Issue
Block a user