feat(core): apply model tracking (#13128)

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

* **New Features**
* Added user interaction tracking for document editing and diff review
actions, including accepting, rejecting, applying, and copying changes.
* Introduced tracking for "Accept all" and "Reject all" actions in block
diff views.

* **Chores**
* Enhanced event tracking system with new event types and payloads to
support detailed analytics for editing and review actions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
德布劳外 · 贾贵
2025-07-10 17:25:05 +08:00
committed by GitHub
parent 3538c78a8b
commit 6b7d1e91e0
4 changed files with 61 additions and 8 deletions

View File

@@ -1,3 +1,4 @@
import track from '@affine/track';
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std'; import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
@@ -203,24 +204,33 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
} }
private async _handleApply(markdown: string) { private async _handleApply(markdown: string) {
if (!this.host) { if (!this.host || this.data.type !== 'tool-result') {
return; return;
} }
track.applyModel.chat.$.apply({
instruction: this.data.args.instructions,
});
await this.blockDiffService?.apply(this.host.store, markdown); await this.blockDiffService?.apply(this.host.store, markdown);
} }
private async _handleReject(changedMarkdown: string) { private async _handleReject(changedMarkdown: string) {
if (!this.host) { if (!this.host || this.data.type !== 'tool-result') {
return; return;
} }
track.applyModel.chat.$.reject({
instruction: this.data.args.instructions,
});
this.blockDiffService?.setChangedMarkdown(changedMarkdown); this.blockDiffService?.setChangedMarkdown(changedMarkdown);
this.blockDiffService?.rejectAll(); this.blockDiffService?.rejectAll();
} }
private async _handleAccept(changedMarkdown: string) { private async _handleAccept(changedMarkdown: string) {
if (!this.host) { if (!this.host || this.data.type !== 'tool-result') {
return; return;
} }
track.applyModel.chat.$.accept({
instruction: this.data.args.instructions,
});
await this.blockDiffService?.apply(this.host.store, changedMarkdown); await this.blockDiffService?.apply(this.host.store, changedMarkdown);
await this.blockDiffService?.acceptAll(this.host.store); await this.blockDiffService?.acceptAll(this.host.store);
} }
@@ -233,6 +243,7 @@ export class DocEditTool extends WithDisposable(ShadowlessElement) {
if (!this.host) { if (!this.host) {
return; return;
} }
track.applyModel.chat.$.copy();
const success = await copyText(removeMarkdownComments(changedMarkdown)); const success = await copyText(removeMarkdownComments(changedMarkdown));
if (success) { if (success) {
this.notificationService.notify({ this.notificationService.notify({

View File

@@ -1,3 +1,4 @@
import track from '@affine/track';
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { CloseIcon, DoneIcon } from '@blocksuite/icons/lit'; import { CloseIcon, DoneIcon } from '@blocksuite/icons/lit';
@@ -50,12 +51,12 @@ export class BlockDiffOptions extends WithDisposable(LitElement) {
accessor onReject!: (op: PatchOp) => void; accessor onReject!: (op: PatchOp) => void;
private readonly _handleAcceptClick = () => { private readonly _handleAcceptClick = () => {
console.log('accept', this.op); track.applyModel.widget.block.accept();
this.onAccept(this.op); this.onAccept(this.op);
}; };
private readonly _handleRejectClick = () => { private readonly _handleRejectClick = () => {
console.log('reject', this.op); track.applyModel.widget.block.reject();
this.onReject(this.op); this.onReject(this.op);
}; };

View File

@@ -1,3 +1,4 @@
import { track } from '@affine/track';
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std'; import { WidgetComponent, WidgetViewExtension } from '@blocksuite/affine/std';
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { import {
@@ -82,6 +83,16 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
diffs[this.currentIndex].scrollIntoView({ behavior: 'smooth' }); diffs[this.currentIndex].scrollIntoView({ behavior: 'smooth' });
} }
async _handleAcceptAll() {
track.applyModel.widget.page.acceptAll();
await this.diffService.acceptAll(this.std.store);
}
_handleRejectAll() {
track.applyModel.widget.page.rejectAll();
this.diffService.rejectAll();
}
get diffService() { get diffService() {
return this.std.get(BlockDiffProvider); return this.std.get(BlockDiffProvider);
} }
@@ -112,7 +123,7 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
</div> </div>
<div <div
class="ai-block-diff-all-option" class="ai-block-diff-all-option"
@click=${() => this.diffService.rejectAll()} @click=${() => this._handleRejectAll()}
> >
${CloseIcon({ ${CloseIcon({
style: `color: ${unsafeCSSVarV2('icon/secondary')}`, style: `color: ${unsafeCSSVarV2('icon/secondary')}`,
@@ -121,7 +132,7 @@ export class AffineBlockDiffWidgetForPage extends WidgetComponent {
</div> </div>
<div <div
class="ai-block-diff-all-option" class="ai-block-diff-all-option"
@click=${() => this.diffService.acceptAll(this.std.store)} @click=${() => this._handleAcceptAll()}
> >
${DoneIcon({ ${DoneIcon({
style: `color: ${unsafeCSSVarV2('icon/activated')}`, style: `color: ${unsafeCSSVarV2('icon/activated')}`,

View File

@@ -207,6 +207,16 @@ type CommentEvents =
| 'resolveComment'; | 'resolveComment';
// END SECTION // END SECTION
// SECTION: apply model
type ApplyModelEvents =
| 'acceptAll'
| 'rejectAll'
| 'accept'
| 'reject'
| 'apply'
| 'copy';
// END SECTION
type UserEvents = type UserEvents =
| GeneralEvents | GeneralEvents
| AppEvents | AppEvents
@@ -231,7 +241,8 @@ type UserEvents =
| IntegrationEvents | IntegrationEvents
| MeetingEvents | MeetingEvents
| MentionEvents | MentionEvents
| WorkspaceEmbeddingEvents; | WorkspaceEmbeddingEvents
| ApplyModelEvents;
interface PageDivision { interface PageDivision {
[page: string]: { [page: string]: {
@@ -564,6 +575,15 @@ interface PageEvents extends PageDivision {
$: ['createDoc']; $: ['createDoc'];
}; };
}; };
applyModel: {
widget: {
page: ['acceptAll', 'rejectAll'];
block: ['accept', 'reject'];
};
chat: {
$: ['apply', 'accept', 'reject', 'copy'];
};
};
} }
type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite'; type OrganizeItemType = 'doc' | 'folder' | 'collection' | 'tag' | 'favorite';
@@ -635,6 +655,13 @@ type RecordingEventArgs = {
option?: 'Auto transcribing' | 'handle transcribing' | 'on' | 'off'; option?: 'Auto transcribing' | 'handle transcribing' | 'on' | 'off';
}; };
type ApplyModelArgs = {
/* ​​User's complete instruction */
instruction?: string;
/* Split individual semantic change requests */
operation?: string;
};
export type EventArgs = { export type EventArgs = {
createWorkspace: { flavour: string }; createWorkspace: { flavour: string };
signIn: AuthArgs; signIn: AuthArgs;
@@ -829,6 +856,9 @@ export type EventArgs = {
}; };
editComment: { type: 'root' | 'node' }; editComment: { type: 'root' | 'node' };
deleteComment: { type: 'root' | 'node' }; deleteComment: { type: 'root' | 'node' };
accept: ApplyModelArgs;
reject: ApplyModelArgs;
apply: ApplyModelArgs;
}; };
// for type checking // for type checking