mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
feat(core): open doc in semantic and keyword result (#13217)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added clickable document titles in AI chat search results, allowing users to open documents directly from chat interactions. * Enhanced interactivity in AI chat by making relevant search result titles visually indicate clickability (pointer cursor). * **Style** * Updated styles to visually highlight clickable search result titles in AI chat results. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -411,6 +411,7 @@ export class ChatPanel extends SignalWatcher(
|
||||
.onEmbeddingProgressChange=${this.onEmbeddingProgressChange}
|
||||
.onContextChange=${this.onContextChange}
|
||||
.width=${this.sidebarWidth}
|
||||
.onOpenDoc=${this.openDoc}
|
||||
></ai-chat-content>`
|
||||
)}
|
||||
</div>`;
|
||||
|
||||
@@ -86,6 +86,9 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
get state() {
|
||||
const { isLast, status } = this;
|
||||
return isLast
|
||||
@@ -146,6 +149,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
|
||||
.notificationService=${this.notificationService}
|
||||
.theme=${this.affineThemeService.appTheme.themeSignal}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></chat-content-stream-objects>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,9 @@ export class AIChatContent extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor onContextChange!: (context: Partial<ChatContextValue>) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor width: Signal<number | undefined> | undefined;
|
||||
|
||||
@@ -378,6 +381,7 @@ export class AIChatContent extends SignalWatcher(
|
||||
.independentMode=${this.independentMode}
|
||||
.messages=${this.messages}
|
||||
.docDisplayService=${this.docDisplayConfig}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></ai-chat-messages>
|
||||
<ai-chat-composer
|
||||
style=${styleMap({
|
||||
|
||||
@@ -206,6 +206,9 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
@query('.chat-panel-messages-container')
|
||||
accessor messagesContainer: HTMLDivElement | null = null;
|
||||
|
||||
@@ -333,6 +336,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
|
||||
.width=${this.width}
|
||||
.independentMode=${this.independentMode}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></chat-message-assistant>`;
|
||||
} else if (isChatAction(item) && this.host) {
|
||||
return html`<chat-message-action
|
||||
|
||||
@@ -58,6 +58,9 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
private renderToolCall(streamObject: StreamObject) {
|
||||
if (streamObject.type !== 'tool-call') {
|
||||
return nothing;
|
||||
@@ -183,11 +186,13 @@ export class ChatContentStreamObjects extends WithDisposable(
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.docDisplayService=${this.docDisplayService}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></doc-semantic-search-result>`;
|
||||
case 'doc_keyword_search':
|
||||
return html`<doc-keyword-search-result
|
||||
.data=${streamObject}
|
||||
.width=${this.width}
|
||||
.onOpenDoc=${this.onOpenDoc}
|
||||
></doc-keyword-search-result>`;
|
||||
case 'doc_read':
|
||||
return html`<doc-read-result
|
||||
|
||||
@@ -2,7 +2,7 @@ import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { PageIcon, SearchIcon } from '@blocksuite/icons/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { ToolResult } from './tool-result-card';
|
||||
@@ -26,12 +26,21 @@ interface DocKeywordSearchToolResult {
|
||||
}
|
||||
|
||||
export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
.doc-keyword-search-result-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor data!: DocKeywordSearchToolCall | DocKeywordSearchToolResult;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor width: Signal<number | undefined> | undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
renderToolCall() {
|
||||
return html`<tool-call-card
|
||||
.name=${`Searching workspace documents for "${this.data.args.query}"`}
|
||||
@@ -47,7 +56,12 @@ export class DocKeywordSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
let results: ToolResult[] = [];
|
||||
try {
|
||||
results = this.data.result.map(item => ({
|
||||
title: item.title,
|
||||
title: html`<span
|
||||
class="doc-keyword-search-result-title"
|
||||
@click=${() => this.onOpenDoc(item.docId)}
|
||||
>
|
||||
${item.title}
|
||||
</span>`,
|
||||
icon: PageIcon(),
|
||||
}));
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { AiEmbeddingIcon, PageIcon } from '@blocksuite/icons/lit';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import type { Signal } from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
|
||||
import type { DocDisplayConfig } from '../ai-chat-chips';
|
||||
@@ -54,6 +54,12 @@ function parseResultContent(content: string) {
|
||||
}
|
||||
|
||||
export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
.doc-semantic-search-result-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor data!: DocSemanticSearchToolCall | DocSemanticSearchToolResult;
|
||||
|
||||
@@ -63,6 +69,9 @@ export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
@property({ attribute: false })
|
||||
accessor docDisplayService!: DocDisplayConfig;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onOpenDoc!: (docId: string, sessionId?: string) => void;
|
||||
|
||||
renderToolCall() {
|
||||
return html`<tool-call-card
|
||||
.name=${`Finding semantically related pages for "${this.data.args.query}"`}
|
||||
@@ -82,7 +91,12 @@ export class DocSemanticSearchResult extends WithDisposable(ShadowlessElement) {
|
||||
.results=${this.data.result
|
||||
.map(result => ({
|
||||
...parseResultContent(result.content),
|
||||
title: this.docDisplayService.getTitle(result.docId),
|
||||
title: html`<span
|
||||
class="doc-semantic-search-result-title"
|
||||
@click=${() => this.onOpenDoc(result.docId)}
|
||||
>
|
||||
${this.docDisplayService.getTitle(result.docId)}
|
||||
</span>`,
|
||||
}))
|
||||
.filter(Boolean)}
|
||||
></tool-result-card>`;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { css, html, nothing, type TemplateResult } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
|
||||
export interface ToolResult {
|
||||
title: string;
|
||||
title: string | TemplateResult<1>;
|
||||
icon?: string | TemplateResult<1>;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ export const Component = () => {
|
||||
const chatToolContainerRef = useRef<HTMLDivElement>(null);
|
||||
const widthSignalRef = useRef<Signal<number>>(signal(0));
|
||||
const client = useCopilotClient();
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
|
||||
const workspaceId = useService(WorkspaceService).workspace.id;
|
||||
|
||||
@@ -173,6 +174,13 @@ export const Component = () => {
|
||||
setStatus(context.status ?? 'idle');
|
||||
}, []);
|
||||
|
||||
const onOpenDoc = useCallback(
|
||||
(docId: string) => {
|
||||
workbench.openDoc(docId, { at: 'active' });
|
||||
},
|
||||
[workbench]
|
||||
);
|
||||
|
||||
const confirmModal = useConfirmModal();
|
||||
const specs = useAISpecs();
|
||||
const mockStd = useMockStd();
|
||||
@@ -208,6 +216,7 @@ export const Component = () => {
|
||||
confirmModal.openConfirmModal
|
||||
);
|
||||
content.createSession = createSession;
|
||||
content.onOpenDoc = onOpenDoc;
|
||||
|
||||
if (!chatContent) {
|
||||
// initial values that won't change
|
||||
@@ -232,6 +241,7 @@ export const Component = () => {
|
||||
confirmModal,
|
||||
onContextChange,
|
||||
specs,
|
||||
onOpenDoc,
|
||||
]);
|
||||
|
||||
// init or update header ai-chat-toolbar
|
||||
|
||||
Reference in New Issue
Block a user