feat(core): ai intelligence track (#13187)

Close [AI-335](https://linear.app/affine-design/issue/AI-335)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added support for an "independent mode" and document-specific context
in AI chat components, allowing enhanced context handling and tracking.
* Introduced new tracking options to distinguish between current
document and general document actions in chat interactions.

* **Improvements**
* More flexible property handling for independent mode and document
context across chat-related components for consistent behavior and
tracking.
* Enhanced tracking system to support additional event categories and
methods for more granular analytics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wu Yue
2025-07-14 14:43:06 +08:00
committed by GitHub
parent d9e8ce802f
commit 65453c31c6
11 changed files with 56 additions and 22 deletions

View File

@@ -80,7 +80,7 @@ export class ChatMessageAssistant extends WithDisposable(ShadowlessElement) {
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor independentMode!: boolean;
accessor independentMode: boolean | undefined;
get state() {
const { isLast, status } = this;

View File

@@ -23,6 +23,12 @@ export class AIChatAddContext extends SignalWatcher(
}
`;
@property({ attribute: false })
accessor docId: string | undefined;
@property({ attribute: false })
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor addChip!: (chip: ChatChip) => Promise<void>;
@@ -69,6 +75,8 @@ export class AIChatAddContext extends SignalWatcher(
createLitPortal({
template: html`
<chat-panel-add-popover
.docId=${this.docId}
.independentMode=${this.independentMode}
.addChip=${this.addChip}
.addImages=${this.addImages}
.searchMenuConfig=${this.searchMenuConfig}

View File

@@ -1,7 +1,7 @@
import { toast } from '@affine/component';
import type { TagMeta } from '@affine/core/components/page-list';
import type { CollectionMeta } from '@affine/core/modules/collection';
import track from '@affine/track';
import track, { type EventArgs } from '@affine/track';
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { scrollbarStyle } from '@blocksuite/affine/shared/styles';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
@@ -120,6 +120,12 @@ export class ChatPanelAddPopover extends SignalWatcher(
private accessor _query = '';
@property({ attribute: false })
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor docId: string | undefined;
@state()
private accessor _searchGroups: MenuGroup[] = [];
@@ -497,7 +503,8 @@ export class ChatPanelAddPopover extends SignalWatcher(
state: 'processing',
});
const mode = this.docDisplayConfig.getDocPrimaryMode(meta.id);
this._track('doc', mode);
const method = meta.id === this.docId ? 'cur-doc' : 'doc';
this._track(method, mode);
};
private readonly _addTagChip = async (tag: TagMeta) => {
@@ -561,10 +568,13 @@ export class ChatPanelAddPopover extends SignalWatcher(
}
private _track(
method: 'doc' | 'file' | 'tags' | 'collections',
method: EventArgs['addEmbeddingDoc']['method'],
type?: 'page' | 'edgeless'
) {
track.$.chatPanel.chatPanelInput.addEmbeddingDoc({
const page = this.independentMode
? track.$.intelligence
: track.$.chatPanel;
page.chatPanelInput.addEmbeddingDoc({
control: 'addButton',
method,
type,

View File

@@ -81,6 +81,9 @@ export class ChatPanelChips extends SignalWatcher(
@property({ attribute: false })
accessor isCollapsed!: boolean;
@property({ attribute: false })
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor addChip!: (chip: ChatChip) => Promise<void>;
@@ -142,6 +145,7 @@ export class ChatPanelChips extends SignalWatcher(
if (isDocChip(chip)) {
return html`<chat-panel-doc-chip
.chip=${chip}
.independentMode=${this.independentMode}
.addChip=${this.addChip}
.updateChip=${this.updateChip}
.removeChip=${this.removeChip}

View File

@@ -18,6 +18,9 @@ export class ChatPanelDocChip extends SignalWatcher(
@property({ attribute: false })
accessor chip!: DocChip;
@property({ attribute: false })
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor addChip!: (chip: DocChip) => void;
@@ -81,7 +84,10 @@ export class ChatPanelDocChip extends SignalWatcher(
state: 'processing',
});
const mode = this.docDisplayConfig.getDocPrimaryMode(this.chip.docId);
track.$.chatPanel.chatPanelInput.addEmbeddingDoc({
const page = this.independentMode
? track.$.intelligence
: track.$.chatPanel;
page.chatPanelInput.addEmbeddingDoc({
control: 'addButton',
method: 'suggestion',
type: mode,

View File

@@ -59,7 +59,7 @@ export class AIChatComposer extends SignalWatcher(
`;
@property({ attribute: false })
accessor independentMode!: boolean;
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
@@ -85,9 +85,9 @@ export class AIChatComposer extends SignalWatcher(
accessor updateContext!: (context: Partial<AIChatInputContext>) => void;
@property({ attribute: false })
accessor onEmbeddingProgressChange!: (
count: Record<ContextEmbedStatus, number>
) => void;
accessor onEmbeddingProgressChange:
| ((count: Record<ContextEmbedStatus, number>) => void)
| undefined;
@property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig;
@@ -136,6 +136,7 @@ export class AIChatComposer extends SignalWatcher(
<chat-panel-chips
.chips=${this.chips}
.isCollapsed=${this.isChipsCollapsed}
.independentMode=${this.independentMode}
.addChip=${this.addChip}
.updateChip=${this.updateChip}
.removeChip=${this.removeChip}
@@ -603,7 +604,7 @@ export class AIChatComposer extends SignalWatcher(
return chip;
});
this.updateChips(nextChips);
this.onEmbeddingProgressChange(count);
this.onEmbeddingProgressChange?.(count);
if (count.processing === 0) {
this._abortPoll();
}

View File

@@ -124,7 +124,7 @@ export class AIChatContent extends SignalWatcher(
`;
@property({ attribute: false })
accessor independentMode!: boolean;
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor onboardingOffsetY!: number;
@@ -177,9 +177,9 @@ export class AIChatContent extends SignalWatcher(
accessor notificationService!: NotificationService;
@property({ attribute: false })
accessor onEmbeddingProgressChange!: (
count: Record<ContextEmbedStatus, number>
) => void;
accessor onEmbeddingProgressChange:
| ((count: Record<ContextEmbedStatus, number>) => void)
| undefined;
@property({ attribute: false })
accessor onContextChange!: (context: Partial<ChatContextValue>) => void;
@@ -403,7 +403,7 @@ export class AIChatContent extends SignalWatcher(
<ai-chat-messages
class=${classMap({
'ai-chat-messages': true,
'independent-mode': this.independentMode,
'independent-mode': !!this.independentMode,
'no-message': this.messages.length === 0,
})}
${ref(this.chatMessagesRef)}

View File

@@ -290,7 +290,7 @@ export class AIChatInput extends SignalWatcher(
`;
@property({ attribute: false })
accessor independentMode!: boolean;
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor host: EditorHost | null | undefined;
@@ -458,6 +458,8 @@ export class AIChatInput extends SignalWatcher(
<div class="chat-panel-input-actions">
<div class="chat-input-icon">
<ai-chat-add-context
.docId=${this.docId}
.independentMode=${this.independentMode}
.addChip=${this.addChip}
.addImages=${this.addImages}
.docDisplayConfig=${this.docDisplayConfig}

View File

@@ -150,7 +150,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
accessor avatarUrl = '';
@property({ attribute: false })
accessor independentMode!: boolean;
accessor independentMode: boolean | undefined;
@property({ attribute: false })
accessor messages!: HistoryMessage[];
@@ -275,7 +275,7 @@ export class AIChatMessages extends WithDisposable(ShadowlessElement) {
<div
class=${classMap({
'chat-panel-messages-container': true,
'independent-mode': this.independentMode,
'independent-mode': !!this.independentMode,
})}
data-testid="chat-panel-messages-container"
@scroll=${() => this._debouncedOnScroll()}