Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 31 additions & 24 deletions injected/src/features/duck-ai-listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,8 @@ export default class DuckAiListener extends ContentFeature {
if (pageDataParsed.content) {
this.pageData = pageDataParsed;

this.promptTelemetry?.sendContextPixelInfo(pageDataParsed, DuckAiPromptTelemetry.CONTEXT_ATTACH_PIXEL_NAME);

// Resolve any pending context promise
if (this.contextPromiseResolve) {
this.contextPromiseResolve(true);
Expand Down Expand Up @@ -817,7 +819,6 @@ export default class DuckAiListener extends ContentFeature {
const handleClick = this.handleSendMessage.bind(this);

sendButton.addEventListener('click', handleClick, true); // Capture phase
// sendButton.addEventListener('click', handleClick, false); // Bubble phase

this.log.info('Set up message interception with multiple event listeners', sendButton);
}
Expand Down Expand Up @@ -983,7 +984,7 @@ export default class DuckAiListener extends ContentFeature {
this.mutationObserver = null;

// Callback function to execute when mutations are observed
const callback = (_, observer) => {
const callback = (/** @type {MutationRecord[]} */ _, observer) => {
this.findTextBox();
this.setupMessageInterception();
if (this.textBox && this.pageData && this.sendButton && !this.hasContextBeenUsed) {
Expand Down Expand Up @@ -1011,13 +1012,17 @@ export default class DuckAiListener extends ContentFeature {
this.textBox = element;
this.log.info('Found AI text box');

// Add enter key handler to call handleSendMessage
element.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
this.log.info('Enter key pressed');
this.handleSendMessage();
}
});
// Add enter key handler using keydown with capture phase
element.addEventListener(
'keydown',
(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
this.log.info('Enter key pressed');
this.handleSendMessage();
}
},
true,
);

// Set up property descriptor to intercept value reads for context appending
this.setupValuePropertyDescriptor(element);
Expand Down Expand Up @@ -1148,7 +1153,8 @@ ${truncatedWarning}
*/
class DuckAiPromptTelemetry {
static STORAGE_KEY = 'aiChatPageContextTelemetry';
static CONTEXT_PIXEL_NAME = 'dc_contextInfo';
static CONTEXT_ATTACH_PIXEL_NAME = 'dc_contextInfoOnAttach';
static CONTEXT_SEND_PIXEL_NAME = 'dc_contextInfoOnSubmit';
static DAILY_PIXEL_NAME = 'dc_pageContextDailyTelemetry';
static ONE_DAY_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

Expand Down Expand Up @@ -1298,11 +1304,11 @@ class DuckAiPromptTelemetry {

const telemetryData = {
totalPrompts: String(totalPrompts),
avgRawPromptSize: this.bucketSizeByThousands(avgRawPromptSize),
avgRawPromptSize: this.bucketSize(avgRawPromptSize),
...createSizeFields('raw', rawSizeBuckets),
avgTotalPromptSize: this.bucketSizeByThousands(avgTotalPromptSize),
avgTotalPromptSize: this.bucketSize(avgTotalPromptSize),
...createSizeFields('total', totalSizeBuckets),
avgContextSize: this.bucketSizeByThousands(avgContextSize),
avgContextSize: this.bucketSize(avgContextSize),
contextUsageRate: String(Math.round(contextUsageRate * 100)),
};

Expand Down Expand Up @@ -1337,7 +1343,8 @@ class DuckAiPromptTelemetry {
if (!globalThis?.DDG?.pixel) {
return;
}
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_PIXEL_NAME] = {};
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_SEND_PIXEL_NAME] = {};
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.CONTEXT_ATTACH_PIXEL_NAME] = {};
globalThis.DDG.pixel._pixels[DuckAiPromptTelemetry.DAILY_PIXEL_NAME] = {};
}

Expand All @@ -1354,30 +1361,30 @@ class DuckAiPromptTelemetry {
}

/**
* Bucket numbers by thousands for privacy-friendly reporting
* Bucket numbers by hundreds for privacy-friendly reporting
* @param {number} number - Number to bucket
* @returns {string} Bucket lower bound (e.g., '0', '1000', '2000')
* @returns {string} Bucket lower bound (e.g., '0', '100', '200')
*/
bucketSizeByThousands(number) {
bucketSize(number) {
if (number <= 0) {
return '0';
}
const bucketIndex = Math.floor(number / 1000);
return String(bucketIndex * 1000);
const bucketIndex = Math.floor(number / 100);
return String(bucketIndex * 100);
}

/**
* Send context pixel info when context is used
* @param {Object} contextData - Context data object
*/
sendContextPixelInfo(contextData) {
if (!contextData?.content) {
sendContextPixelInfo(contextData, pixelName) {
if (!contextData?.content || contextData.content.length === 0) {
this.log.warn('sendContextPixelInfo: No content available for pixel tracking');
return;
}

this.sendPixel(DuckAiPromptTelemetry.CONTEXT_PIXEL_NAME, {
contextLength: this.bucketSizeByThousands(contextData.content.length),
this.sendPixel(pixelName, {
contextLength: contextData.content.length,
});
}

Expand Down Expand Up @@ -1406,7 +1413,7 @@ class DuckAiPromptTelemetry {

// Send context pixel info if context was used
if (contextData && contextSize > 0) {
this.sendContextPixelInfo(contextData);
this.sendContextPixelInfo(contextData, DuckAiPromptTelemetry.CONTEXT_SEND_PIXEL_NAME);
}
// Check if we should fire daily telemetry
this.checkShouldFireDailyTelemetry();
Expand Down
Loading