3
3
// found in the LICENSE file.
4
4
5
5
import * as Lit from '../../../ui/lit/lit.js' ;
6
+ const { html} = Lit ;
7
+
8
+ // Constants
9
+ const PROMPT_CONSTANTS = {
10
+ DOUBLE_CLICK_DELAY : 300 ,
11
+ CUSTOM_PROMPTS_STORAGE_KEY : 'ai_chat_custom_prompts' ,
12
+ } as const ;
6
13
7
14
// Direct imports from Tools.ts
8
15
import { ToolRegistry } from '../agent_framework/ConfigurableAgentTool.js' ;
@@ -23,8 +30,6 @@ import {
23
30
// Initialize configured agents
24
31
initializeConfiguredAgents ( ) ;
25
32
26
- const { html} = Lit ;
27
-
28
33
// Define available agent types
29
34
export enum BaseOrchestratorAgentType {
30
35
SEARCH = 'search' ,
@@ -67,7 +72,21 @@ Present your findings in a structured markdown report with:
67
72
7. **Conclusions**: Summary of the most reliable answers based on the research
68
73
8. **References**: Full citation list of all sources consulted
69
74
70
- Maintain objectivity throughout your research process and clearly distinguish between well-established facts and more speculative information. When appropriate, note areas where more research might be needed. Note: the final report should be alteast 5000 words or even longer based on the topic, if there is not enough content do more research.` ,
75
+ Maintain objectivity throughout your research process and clearly distinguish between well-established facts and more speculative information. When appropriate, note areas where more research might be needed. Note: the final report should be at least 5000 words or even longer based on the topic, if there is not enough content do more research.
76
+
77
+ ## CRITICAL: Final Output Format
78
+
79
+ When calling 'finalize_with_critique', you MUST structure your response in this exact XML format:
80
+
81
+ <reasoning>
82
+ [Provide 2-3 sentences explaining your research approach, key insights discovered, and how you organized the information]
83
+ </reasoning>
84
+
85
+ <markdown_report>
86
+ [Your comprehensive markdown report goes here - this will be automatically extracted and displayed in an enhanced document viewer]
87
+ </markdown_report>
88
+
89
+ The markdown report section will be hidden from the chat interface and displayed with an enhanced document viewer button. Only the reasoning will be shown in the chat.` ,
71
90
72
91
[ BaseOrchestratorAgentType . SHOPPING ] : `You are a **Shopping Research Agent**. Your mission is to help users find and compare products tailored to their specific needs and budget, providing up-to-date, unbiased, and well-cited recommendations.
73
92
@@ -217,6 +236,11 @@ export const AGENT_CONFIGS: {[key: string]: AgentConfig} = {
217
236
* Get the system prompt for a specific agent type
218
237
*/
219
238
export function getSystemPrompt ( agentType : string ) : string {
239
+ // Check if there's a custom prompt for this agent type
240
+ if ( hasCustomPrompt ( agentType ) ) {
241
+ return getAgentPrompt ( agentType ) ;
242
+ }
243
+
220
244
return AGENT_CONFIGS [ agentType ] ?. systemPrompt ||
221
245
// Default system prompt if agent type not found
222
246
`
@@ -295,17 +319,30 @@ export function renderAgentTypeButtons(
295
319
) : Lit . TemplateResult {
296
320
return html `
297
321
< div class ="prompt-buttons-container ">
298
- ${ Object . values ( AGENT_CONFIGS ) . map ( config => html `
322
+ ${ Object . values ( AGENT_CONFIGS ) . map ( config => {
323
+ const isCustomized = hasCustomPrompt ( config . type ) ;
324
+ const buttonClasses = [
325
+ 'prompt-button' ,
326
+ selectedAgentType === config . type ? 'selected' : '' ,
327
+ isCustomized ? 'customized' : ''
328
+ ] . filter ( Boolean ) . join ( ' ' ) ;
329
+
330
+ const title = isCustomized ?
331
+ `${ config . description || config . label } (Custom prompt - double-click to edit)` :
332
+ `${ config . description || config . label } (Double-click to edit prompt)` ;
333
+
334
+ return html `
299
335
< button
300
- class =" prompt-button ${ selectedAgentType === config . type ? 'selected' : '' } "
336
+ class =${ buttonClasses }
301
337
data-agent-type =${ config . type }
302
338
@click=${ handleClick }
303
- title=${ config . description || config . label }
339
+ title=${ title }
304
340
>
305
341
< span class ="prompt-icon "> ${ config . icon } </ span >
306
342
${ showLabels ? html `< span class ="prompt-label "> ${ config . label } </ span > ` : Lit . nothing }
343
+ ${ isCustomized ? html `< span class ="prompt-custom-indicator "> ●</ span > ` : Lit . nothing }
307
344
</ button >
308
- ` ) }
345
+ ` } ) }
309
346
</ div >
310
347
` ;
311
348
}
@@ -316,38 +353,151 @@ export function createAgentTypeSelectionHandler(
316
353
textInputElement : HTMLTextAreaElement | undefined ,
317
354
onAgentTypeSelected : ( ( agentType : string | null ) => void ) | undefined ,
318
355
setSelectedAgentType : ( type : string | null ) => void ,
319
- getCurrentSelectedType : ( ) => string | null
356
+ getCurrentSelectedType : ( ) => string | null ,
357
+ onAgentPromptEdit ?: ( agentType : string ) => void
320
358
) : ( event : Event ) => void {
359
+ let clickTimeout : number | null = null ;
360
+ let clickCount = 0 ;
361
+
321
362
return ( event : Event ) : void => {
322
363
const button = event . currentTarget as HTMLButtonElement ;
323
364
const agentType = button . dataset . agentType ;
324
365
if ( agentType && onAgentTypeSelected ) {
325
- const currentSelected = getCurrentSelectedType ( ) ;
366
+ clickCount ++ ;
326
367
327
- // Remove selected class from all agent type buttons
328
- const allButtons = element . shadowRoot ?. querySelectorAll ( '.prompt-button' ) ;
329
- allButtons ?. forEach ( btn => btn . classList . remove ( 'selected' ) ) ;
330
-
331
- // Check if we're clicking on the currently selected button (toggle off)
332
- if ( currentSelected === agentType ) {
333
- // Deselect - set to null and don't add selected class
334
- setSelectedAgentType ( null ) ;
335
- onAgentTypeSelected ( null ) ;
336
- console . log ( 'Deselected agent type, returning to default' ) ;
337
- } else {
338
- // Select new agent type - add selected class to clicked button
339
- button . classList . add ( 'selected' ) ;
340
- setSelectedAgentType ( agentType ) ;
341
- onAgentTypeSelected ( agentType ) ;
342
- console . log ( 'Selected agent type:' , agentType ) ;
368
+ // Clear existing timeout
369
+ if ( clickTimeout ) {
370
+ clearTimeout ( clickTimeout ) ;
343
371
}
344
-
345
- // Focus the input after selecting/deselecting an agent type
346
- textInputElement ?. focus ( ) ;
372
+
373
+ // Set timeout to distinguish between single and double click
374
+ clickTimeout = window . setTimeout ( ( ) => {
375
+ if ( clickCount === 1 ) {
376
+ // Single click - handle selection/deselection
377
+ const currentSelected = getCurrentSelectedType ( ) ;
378
+
379
+ // Remove selected class from all agent type buttons
380
+ const allButtons = element . shadowRoot ?. querySelectorAll ( '.prompt-button' ) ;
381
+ allButtons ?. forEach ( btn => btn . classList . remove ( 'selected' ) ) ;
382
+
383
+ // Check if we're clicking on the currently selected button (toggle off)
384
+ if ( currentSelected === agentType ) {
385
+ // Deselect - set to null and don't add selected class
386
+ setSelectedAgentType ( null ) ;
387
+ onAgentTypeSelected ( null ) ;
388
+ console . log ( 'Deselected agent type, returning to default' ) ;
389
+ } else {
390
+ // Select new agent type - add selected class to clicked button
391
+ button . classList . add ( 'selected' ) ;
392
+ setSelectedAgentType ( agentType ) ;
393
+ onAgentTypeSelected ( agentType ) ;
394
+ console . log ( 'Selected agent type:' , agentType ) ;
395
+ }
396
+
397
+ // Focus the input after selecting/deselecting an agent type
398
+ textInputElement ?. focus ( ) ;
399
+ } else if ( clickCount === 2 && onAgentPromptEdit ) {
400
+ // Double click - handle prompt editing
401
+ console . log ( 'Double-clicked agent type for prompt editing:' , agentType ) ;
402
+ onAgentPromptEdit ( agentType ) ;
403
+ }
404
+
405
+ clickCount = 0 ;
406
+ clickTimeout = null ;
407
+ } , PROMPT_CONSTANTS . DOUBLE_CLICK_DELAY ) ;
347
408
}
348
409
} ;
349
410
}
350
411
412
+ // Prompt management functions
413
+
414
+ /**
415
+ * Get the current prompt for an agent type (custom or default)
416
+ */
417
+ export function getAgentPrompt ( agentType : string ) : string {
418
+ const customPrompts = getCustomPrompts ( ) ;
419
+ return customPrompts [ agentType ] || SYSTEM_PROMPTS [ agentType as keyof typeof SYSTEM_PROMPTS ] || '' ;
420
+ }
421
+
422
+ /**
423
+ * Set a custom prompt for an agent type
424
+ */
425
+ export function setCustomPrompt ( agentType : string , prompt : string ) : void {
426
+ try {
427
+ const customPrompts = getCustomPrompts ( ) ;
428
+ customPrompts [ agentType ] = prompt ;
429
+ localStorage . setItem ( PROMPT_CONSTANTS . CUSTOM_PROMPTS_STORAGE_KEY , JSON . stringify ( customPrompts ) ) ;
430
+ } catch ( error ) {
431
+ console . error ( 'Failed to save custom prompt:' , error ) ;
432
+ throw error ;
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Remove custom prompt for an agent type (restore to default)
438
+ */
439
+ export function removeCustomPrompt ( agentType : string ) : void {
440
+ try {
441
+ const customPrompts = getCustomPrompts ( ) ;
442
+ delete customPrompts [ agentType ] ;
443
+ localStorage . setItem ( PROMPT_CONSTANTS . CUSTOM_PROMPTS_STORAGE_KEY , JSON . stringify ( customPrompts ) ) ;
444
+ } catch ( error ) {
445
+ console . error ( 'Failed to remove custom prompt:' , error ) ;
446
+ throw error ;
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Check if an agent type has a custom prompt
452
+ */
453
+ export function hasCustomPrompt ( agentType : string ) : boolean {
454
+ const customPrompts = getCustomPrompts ( ) ;
455
+ return agentType in customPrompts ;
456
+ }
457
+
458
+ /**
459
+ * Get all custom prompts from localStorage
460
+ */
461
+ function getCustomPrompts ( ) : { [ key : string ] : string } {
462
+ try {
463
+ const stored = localStorage . getItem ( PROMPT_CONSTANTS . CUSTOM_PROMPTS_STORAGE_KEY ) ;
464
+ if ( ! stored ) {
465
+ return { } ;
466
+ }
467
+ const parsed = JSON . parse ( stored ) ;
468
+ // Validate that it's an object with string values
469
+ if ( typeof parsed !== 'object' || parsed === null ) {
470
+ console . warn ( 'Invalid custom prompts format, resetting' ) ;
471
+ return { } ;
472
+ }
473
+ // Ensure all values are strings
474
+ const validated : { [ key : string ] : string } = { } ;
475
+ for ( const [ key , value ] of Object . entries ( parsed ) ) {
476
+ if ( typeof value === 'string' ) {
477
+ validated [ key ] = value ;
478
+ }
479
+ }
480
+ return validated ;
481
+ } catch ( error ) {
482
+ console . error ( 'Error loading custom prompts:' , error ) ;
483
+ return { } ;
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Get the default prompt for an agent type
489
+ */
490
+ export function getDefaultPrompt ( agentType : string ) : string {
491
+ return SYSTEM_PROMPTS [ agentType as keyof typeof SYSTEM_PROMPTS ] || '' ;
492
+ }
493
+
494
+ /**
495
+ * Type guard to check if an agent type is valid
496
+ */
497
+ export function isValidAgentType ( agentType : string ) : agentType is BaseOrchestratorAgentType {
498
+ return Object . values ( BaseOrchestratorAgentType ) . includes ( agentType as BaseOrchestratorAgentType ) ;
499
+ }
500
+
351
501
declare global {
352
502
interface HTMLElementEventMap {
353
503
[ AgentTypeSelectionEvent . eventName ] : AgentTypeSelectionEvent ;
0 commit comments