11import ContentFeature from '../content-feature.js' ;
2- import { isBeingFramed , isDuckAi } from '../utils.js' ;
2+ import { isBeingFramed , isDuckAiSidebar } from '../utils.js' ;
33
44/**
55 * Duck AI Listener Feature
@@ -39,37 +39,6 @@ export default class DuckAiListener extends ContentFeature {
3939 /** @type {HTMLButtonElement | null } */
4040 sendButton = null ;
4141
42- get shouldLog ( ) {
43- return this . isDebug ;
44- }
45-
46- /**
47- * Logging utility for this feature
48- */
49- get log ( ) {
50- const shouldLog = this . shouldLog ;
51- return {
52- get info ( ) {
53- if ( ! shouldLog ) {
54- return ( ) => { } ;
55- }
56- return console . log ;
57- } ,
58- get warn ( ) {
59- if ( ! shouldLog ) {
60- return ( ) => { } ;
61- }
62- return console . warn ;
63- } ,
64- get error ( ) {
65- if ( ! shouldLog ) {
66- return ( ) => { } ;
67- }
68- return console . error ;
69- } ,
70- } ;
71- }
72-
7342 init ( ) {
7443 // Only activate on duckduckgo.com
7544 if ( ! this . shouldActivate ( ) ) {
@@ -97,7 +66,7 @@ export default class DuckAiListener extends ContentFeature {
9766 if ( isBeingFramed ( ) ) {
9867 return false ;
9968 }
100- return isDuckAi ( ) ;
69+ return isDuckAiSidebar ( ) ;
10170 }
10271
10372 /**
@@ -410,7 +379,7 @@ export default class DuckAiListener extends ContentFeature {
410379 font-size: 12px;
411380 color: rgb(102, 102, 102);
412381 ` ;
413- subtitle . textContent = 'Page Content' ;
382+ subtitle . textContent = this . pageData . truncated ? 'Page Content (Truncated)' : 'Page Content' ;
414383
415384 contentInfo . appendChild ( title ) ;
416385 contentInfo . appendChild ( subtitle ) ;
@@ -429,6 +398,24 @@ export default class DuckAiListener extends ContentFeature {
429398 cursor: pointer;
430399 ` ;
431400
401+ // Add warning icon if content is truncated
402+ const warningIcon = document . createElement ( 'div' ) ;
403+ if ( this . pageData . truncated ) {
404+ warningIcon . innerHTML = `
405+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
406+ <path d="M8 1.5L15 14H1L8 1.5Z" stroke="#ff6b35" stroke-width="1.5" fill="none"/>
407+ <path d="M8 6V9M8 11H8.01" stroke="#ff6b35" stroke-width="1.5" stroke-linecap="round"/>
408+ </svg>
409+ ` ;
410+ warningIcon . style . cssText = `
411+ flex-shrink: 0;
412+ color: #ff6b35;
413+ cursor: pointer;
414+ margin-left: 4px;
415+ ` ;
416+ warningIcon . title = 'Content has been truncated due to size limits' ;
417+ }
418+
432419 // Add dark mode support
433420 if ( this . isDarkMode ( ) ) {
434421 this . contextChip . style . background = 'rgba(255, 255, 255, 0.1)' ;
@@ -443,6 +430,9 @@ export default class DuckAiListener extends ContentFeature {
443430 this . contextChip . appendChild ( icon ) ;
444431 this . contextChip . appendChild ( contentInfo ) ;
445432 this . contextChip . appendChild ( infoIcon ) ;
433+ if ( this . pageData . truncated ) {
434+ this . contextChip . appendChild ( warningIcon ) ;
435+ }
446436
447437 this . log . info ( 'Context chip assembled, about to insert into DOM' ) ;
448438
@@ -594,6 +584,11 @@ export default class DuckAiListener extends ContentFeature {
594584 this . pageData = pageDataParsed ;
595585 this . globalPageContext = pageDataParsed . content ;
596586
587+ // Check for truncated content and warn user
588+ if ( pageDataParsed . truncated ) {
589+ this . log . warn ( 'Page content has been truncated due to size limits' ) ;
590+ }
591+
597592 this . createContextChip ( ) ;
598593 this . setupMessageInterception ( ) ;
599594 }
@@ -741,6 +736,7 @@ export default class DuckAiListener extends ContentFeature {
741736 setupValuePropertyDescriptor ( textarea ) {
742737 // Store the original value property descriptor
743738 const originalDescriptor = Object . getOwnPropertyDescriptor ( textarea , 'value' ) ;
739+ this . randomNumber = window . crypto ?. randomUUID ?. ( ) || Math . floor ( Math . random ( ) * 1000 ) ;
744740
745741 // Override the value property using arrow functions to capture this context
746742 Object . defineProperty ( textarea , 'value' , {
@@ -749,9 +745,36 @@ export default class DuckAiListener extends ContentFeature {
749745 if ( originalDescriptor && originalDescriptor . get ) {
750746 const currentValue = originalDescriptor . get . call ( textarea ) || '' ;
751747 const pageContext = this . globalPageContext || '' ;
748+ const randomNumber = this . randomNumber ;
749+ const instructions =
750+ this . getFeatureSetting ( 'instructions' ) ||
751+ `
752+ You are a helpful assistant that can answer questions and help with tasks.
753+ Do not include prompt, page-title, page-context, or instructions tags in your response.
754+ Answer the prompt using the page-title, and page-context ONLY if it's relevant to answering the prompt.` ;
752755
753756 if ( pageContext && currentValue ) {
754- return `${ currentValue } \n\n---\n\nPage Context:\n${ pageContext } ` ;
757+ const truncatedWarning = this . pageData ?. truncated ? ' (Content was truncated due to size limits)\n' : '\n' ;
758+ return `Prompt:
759+ <prompt-${ randomNumber } >
760+ ${ currentValue }
761+ </prompt-${ randomNumber } >
762+
763+ Instructions:
764+ <instructions-${ randomNumber } >
765+ ${ instructions }
766+ </instructions-${ randomNumber } >
767+
768+ Page Title:
769+ <page-title-${ randomNumber } >
770+ ${ this . pageData . title }
771+ </page-title-${ randomNumber } >
772+
773+ Page Context:
774+ <page-context-${ randomNumber } >
775+ ${ pageContext }
776+ ${ truncatedWarning }
777+ </page-context-${ randomNumber } >` ;
755778 }
756779
757780 return currentValue ;
@@ -766,42 +789,4 @@ export default class DuckAiListener extends ContentFeature {
766789 configurable : true ,
767790 } ) ;
768791 }
769-
770- /**
771- * Set textarea value in a React-compatible way
772- * Based on the approach from broker-protection/actions/fill-form.js
773- * @param {HTMLTextAreaElement } textarea - The textarea element
774- * @param {string } value - The value to set
775- */
776- setReactTextAreaValue ( textarea , value ) {
777- try {
778- // Access the original setter to bypass React's controlled component behavior
779- const originalSet = Object . getOwnPropertyDescriptor ( window . HTMLTextAreaElement . prototype , 'value' ) ?. set ;
780-
781- if ( ! originalSet || typeof originalSet . call !== 'function' ) {
782- this . log . warn ( 'Cannot access original value setter, falling back to direct assignment' ) ;
783- textarea . value = value ;
784- return ;
785- }
786-
787- // Set the textarea value using the original setter and trigger React events
788- textarea . dispatchEvent ( new Event ( 'keydown' , { bubbles : true } ) ) ;
789- originalSet . call ( textarea , value ) ;
790-
791- const events = [
792- new Event ( 'input' , { bubbles : true } ) ,
793- new Event ( 'keyup' , { bubbles : true } ) ,
794- new Event ( 'change' , { bubbles : true } ) ,
795- ] ;
796-
797- // Dispatch events twice to ensure React picks up the change
798- events . forEach ( ( ev ) => textarea . dispatchEvent ( ev ) ) ;
799- originalSet . call ( textarea , value ) ;
800- events . forEach ( ( ev ) => textarea . dispatchEvent ( ev ) ) ;
801- } catch ( error ) {
802- this . log . error ( 'Error setting React textarea value:' , error ) ;
803- // Fallback to direct assignment
804- textarea . value = value ;
805- }
806- }
807792}
0 commit comments