33// found in the LICENSE file.
44
55import * 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 ;
613
714// Direct imports from Tools.ts
815import { ToolRegistry } from '../agent_framework/ConfigurableAgentTool.js' ;
@@ -23,8 +30,6 @@ import {
2330// Initialize configured agents
2431initializeConfiguredAgents ( ) ;
2532
26- const { html} = Lit ;
27-
2833// Define available agent types
2934export enum BaseOrchestratorAgentType {
3035 SEARCH = 'search' ,
@@ -67,7 +72,21 @@ Present your findings in a structured markdown report with:
67727. **Conclusions**: Summary of the most reliable answers based on the research
68738. **References**: Full citation list of all sources consulted
6974
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.` ,
7190
7291 [ 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.
7392
@@ -217,6 +236,11 @@ export const AGENT_CONFIGS: {[key: string]: AgentConfig} = {
217236 * Get the system prompt for a specific agent type
218237 */
219238export 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+
220244 return AGENT_CONFIGS [ agentType ] ?. systemPrompt ||
221245 // Default system prompt if agent type not found
222246 `
@@ -295,17 +319,30 @@ export function renderAgentTypeButtons(
295319) : Lit . TemplateResult {
296320 return html `
297321 < 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 `
299335 < button
300- class =" prompt-button ${ selectedAgentType === config . type ? 'selected' : '' } "
336+ class =${ buttonClasses }
301337 data-agent-type =${ config . type }
302338 @click=${ handleClick }
303- title=${ config . description || config . label }
339+ title=${ title }
304340 >
305341 < span class ="prompt-icon "> ${ config . icon } </ span >
306342 ${ showLabels ? html `< span class ="prompt-label "> ${ config . label } </ span > ` : Lit . nothing }
343+ ${ isCustomized ? html `< span class ="prompt-custom-indicator "> ●</ span > ` : Lit . nothing }
307344 </ button >
308- ` ) }
345+ ` } ) }
309346 </ div >
310347 ` ;
311348}
@@ -316,38 +353,151 @@ export function createAgentTypeSelectionHandler(
316353 textInputElement : HTMLTextAreaElement | undefined ,
317354 onAgentTypeSelected : ( ( agentType : string | null ) => void ) | undefined ,
318355 setSelectedAgentType : ( type : string | null ) => void ,
319- getCurrentSelectedType : ( ) => string | null
356+ getCurrentSelectedType : ( ) => string | null ,
357+ onAgentPromptEdit ?: ( agentType : string ) => void
320358) : ( event : Event ) => void {
359+ let clickTimeout : number | null = null ;
360+ let clickCount = 0 ;
361+
321362 return ( event : Event ) : void => {
322363 const button = event . currentTarget as HTMLButtonElement ;
323364 const agentType = button . dataset . agentType ;
324365 if ( agentType && onAgentTypeSelected ) {
325- const currentSelected = getCurrentSelectedType ( ) ;
366+ clickCount ++ ;
326367
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 ) ;
343371 }
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 ) ;
347408 }
348409 } ;
349410}
350411
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+
351501declare global {
352502 interface HTMLElementEventMap {
353503 [ AgentTypeSelectionEvent . eventName ] : AgentTypeSelectionEvent ;
0 commit comments