1
1
import ContentFeature from '../content-feature.js' ;
2
- import { isBeingFramed , isDuckAi } from '../utils.js' ;
2
+ import { isBeingFramed , isDuckAiSidebar } from '../utils.js' ;
3
3
4
4
/**
5
5
* Duck AI Listener Feature
@@ -39,37 +39,6 @@ export default class DuckAiListener extends ContentFeature {
39
39
/** @type {HTMLButtonElement | null } */
40
40
sendButton = null ;
41
41
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
-
73
42
init ( ) {
74
43
// Only activate on duckduckgo.com
75
44
if ( ! this . shouldActivate ( ) ) {
@@ -97,7 +66,7 @@ export default class DuckAiListener extends ContentFeature {
97
66
if ( isBeingFramed ( ) ) {
98
67
return false ;
99
68
}
100
- return isDuckAi ( ) ;
69
+ return isDuckAiSidebar ( ) ;
101
70
}
102
71
103
72
/**
@@ -410,7 +379,7 @@ export default class DuckAiListener extends ContentFeature {
410
379
font-size: 12px;
411
380
color: rgb(102, 102, 102);
412
381
` ;
413
- subtitle . textContent = 'Page Content' ;
382
+ subtitle . textContent = this . pageData . truncated ? 'Page Content (Truncated)' : 'Page Content' ;
414
383
415
384
contentInfo . appendChild ( title ) ;
416
385
contentInfo . appendChild ( subtitle ) ;
@@ -429,6 +398,24 @@ export default class DuckAiListener extends ContentFeature {
429
398
cursor: pointer;
430
399
` ;
431
400
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
+
432
419
// Add dark mode support
433
420
if ( this . isDarkMode ( ) ) {
434
421
this . contextChip . style . background = 'rgba(255, 255, 255, 0.1)' ;
@@ -443,6 +430,9 @@ export default class DuckAiListener extends ContentFeature {
443
430
this . contextChip . appendChild ( icon ) ;
444
431
this . contextChip . appendChild ( contentInfo ) ;
445
432
this . contextChip . appendChild ( infoIcon ) ;
433
+ if ( this . pageData . truncated ) {
434
+ this . contextChip . appendChild ( warningIcon ) ;
435
+ }
446
436
447
437
this . log . info ( 'Context chip assembled, about to insert into DOM' ) ;
448
438
@@ -594,6 +584,11 @@ export default class DuckAiListener extends ContentFeature {
594
584
this . pageData = pageDataParsed ;
595
585
this . globalPageContext = pageDataParsed . content ;
596
586
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
+
597
592
this . createContextChip ( ) ;
598
593
this . setupMessageInterception ( ) ;
599
594
}
@@ -741,6 +736,7 @@ export default class DuckAiListener extends ContentFeature {
741
736
setupValuePropertyDescriptor ( textarea ) {
742
737
// Store the original value property descriptor
743
738
const originalDescriptor = Object . getOwnPropertyDescriptor ( textarea , 'value' ) ;
739
+ this . randomNumber = window . crypto ?. randomUUID ?. ( ) || Math . floor ( Math . random ( ) * 1000 ) ;
744
740
745
741
// Override the value property using arrow functions to capture this context
746
742
Object . defineProperty ( textarea , 'value' , {
@@ -749,9 +745,36 @@ export default class DuckAiListener extends ContentFeature {
749
745
if ( originalDescriptor && originalDescriptor . get ) {
750
746
const currentValue = originalDescriptor . get . call ( textarea ) || '' ;
751
747
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.` ;
752
755
753
756
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 } >` ;
755
778
}
756
779
757
780
return currentValue ;
@@ -766,42 +789,4 @@ export default class DuckAiListener extends ContentFeature {
766
789
configurable : true ,
767
790
} ) ;
768
791
}
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
- }
807
792
}
0 commit comments