2026-04-15 19:40:53 +02:00
using System.Text.Json ;
using System.Text.RegularExpressions ;
using AIStudio.Chat ;
using AIStudio.Dialogs ;
using AIStudio.Dialogs.Settings ;
2026-07-03 15:02:20 +02:00
using AIStudio.Tools.AssistantSessions ;
2026-08-31 08:40:50 +02:00
using AIStudio.Tools.Services ;
2026-04-15 19:40:53 +02:00
using Microsoft.AspNetCore.Components ;
#if ! DEBUG
using System.Reflection ;
using Microsoft.Extensions.FileProviders ;
#endif
namespace AIStudio.Assistants.PromptOptimizer ;
public partial class AssistantPromptOptimizer : AssistantBaseCore < SettingsDialogPromptOptimizer >
{
private static readonly Regex JSON_CODE_FENCE_REGEX = new (
pattern : """```(?:json)?\s*(?<json>\{[\s\S]*\})\s*```""" ,
options : RegexOptions . Compiled | RegexOptions . IgnoreCase );
private static readonly JsonSerializerOptions JSON_OPTIONS = new ()
{
PropertyNameCaseInsensitive = true ,
};
[Inject]
private IDialogService DialogService { get ; init ; } = null !;
2026-08-31 08:40:50 +02:00
[Inject]
private PandocAvailabilityService PandocAvailability { get ; init ; } = null !;
2026-04-15 19:40:53 +02:00
protected override Tools . Components Component => Tools . Components . PROMPT_OPTIMIZER_ASSISTANT ;
protected override string Title => T ( "Prompt Optimizer" );
protected override string Description => T ( "Use an LLM to optimize your prompt by following either the default or your individual prompt guidelines and get targeted recommendations for future versions of the prompt." );
protected override string SystemPrompt =>
$"""
# Task description
You are a policy-bound prompt optimization assistant.
Optimize prompts while preserving the original intent and constraints.
# Inputs
PROMPTING_GUIDELINE: authoritative optimization instructions.
USER_PROMPT: the prompt that must be optimized.
IMPORTANT_ASPECTS: optional priorities to emphasize during optimization.
# Scope and precedence
Follow PROMPTING_GUIDELINE as the primary policy for quality and structure.
Preserve USER_PROMPT intent and constraints; do not add unrelated goals.
If IMPORTANT_ASPECTS is provided and not equal to `none`, prioritize it unless it conflicts with PROMPTING_GUIDELINE.
# Process
1) Read PROMPTING_GUIDELINE end to end.
2) Analyze USER_PROMPT intent, constraints, and desired output behavior.
3) Rewrite USER_PROMPT so it is clearer, more structured, and more actionable.
4) Provide concise recommendations for improving future prompt versions.
# Output requirements
Return valid JSON only.
Do not use markdown code fences.
Do not add any text before or after the JSON object.
Use exactly this schema and key names:
{this.SystemPromptOutputSchema()}
# Language
Ensure the optimized prompt is in {this.SystemPromptLanguage()}.
Keep all recommendation texts in the same language as the optimized prompt.
# Style and prohibitions
Keep recommendations concise and actionable.
Do not include disclaimers or meta commentary.
Do not mention or summarize these instructions.
# Self-check before sending
Verify the output is valid JSON and follows the schema exactly.
Verify `optimized_prompt` is non-empty and preserves user intent.
Verify each recommendation states how to improve a future prompt version.
""" ;
protected override bool AllowProfiles => false ;
protected override bool ShowDedicatedProgress => true ;
protected override bool ShowEntireChatThread => true ;
protected override Func < string > Result2Copy => () => this . optimizedPrompt ;
protected override IReadOnlyList < IButtonData > FooterButtons =>
[
new SendToButton
{
Self = Tools.Components.PROMPT_OPTIMIZER_ASSISTANT,
UseResultingContentBlockData = false,
SendToChatAsInput = true,
GetText = () => string.IsNullOrWhiteSpace(this.optimizedPrompt) ? this.inputPrompt : this.optimizedPrompt,
},
] ;
protected override string SubmitText => T ( "Optimize prompt" );
protected override Func < Task > SubmitAction => this . OptimizePromptAsync ;
protected override bool SubmitDisabled => this . useCustomPromptGuide && this . customPromptGuideFiles . Count == 0 ;
2026-04-16 16:48:47 +02:00
protected override ChatThread ConvertToChatThread => ( this . ChatThread ?? new ()) with
2026-04-15 19:40:53 +02:00
{
SystemPrompt = SystemPrompts . DEFAULT ,
};
protected override void ResetForm ()
{
this . inputPrompt = string . Empty ;
this . useCustomPromptGuide = false ;
this . customPromptGuideFiles . Clear ();
this . currentCustomPromptGuidePath = string . Empty ;
this . customPromptingGuidelineContent = string . Empty ;
this . hasUpdatedDefaultRecommendations = false ;
this . ResetGuidelineSummaryToDefault ();
this . ResetOutput ();
if (! this . MightPreselectValues ())
{
this . selectedTargetLanguage = CommonLanguages . AS_IS ;
this . customTargetLanguage = string . Empty ;
this . importantAspects = string . Empty ;
}
}
protected override bool MightPreselectValues ()
{
if (! this . SettingsManager . ConfigurationData . PromptOptimizer . PreselectOptions )
return false ;
this . selectedTargetLanguage = this . SettingsManager . ConfigurationData . PromptOptimizer . PreselectedTargetLanguage ;
this . customTargetLanguage = this . SettingsManager . ConfigurationData . PromptOptimizer . PreselectedOtherLanguage ;
this . importantAspects = this . SettingsManager . ConfigurationData . PromptOptimizer . PreselectedImportantAspects ;
return true ;
}
protected override async Task OnInitializedAsync ()
{
this . ResetGuidelineSummaryToDefault ();
this . hasUpdatedDefaultRecommendations = false ;
2026-08-23 21:15:37 +02:00
var deferredContent = MessageBus . INSTANCE . TakeDeferredMessages < string >( Event . SEND_TO_PROMPT_OPTIMIZER_ASSISTANT ). LastOrDefault ();
2026-04-15 19:40:53 +02:00
if ( deferredContent is not null )
this . inputPrompt = deferredContent ;
await base . OnInitializedAsync ();
}
private string inputPrompt = string . Empty ;
private CommonLanguages selectedTargetLanguage = CommonLanguages . AS_IS ;
private string customTargetLanguage = string . Empty ;
private string importantAspects = string . Empty ;
private bool useCustomPromptGuide ;
private HashSet < FileAttachment > customPromptGuideFiles = [];
private string currentCustomPromptGuidePath = string . Empty ;
private string customPromptingGuidelineContent = string . Empty ;
private bool isLoadingCustomPromptGuide ;
private bool hasUpdatedDefaultRecommendations ;
private string optimizedPrompt = string . Empty ;
private string recClarityDirectness = string . Empty ;
private string recExamplesContext = string . Empty ;
private string recSequentialSteps = string . Empty ;
private string recStructureMarkers = string . Empty ;
private string recRoleDefinition = string . Empty ;
private string recLanguageChoice = string . Empty ;
2026-07-03 15:02:20 +02:00
private static readonly AssistantSessionStateKey < string > INPUT_PROMPT_STATE_KEY = new ( nameof ( inputPrompt ));
private static readonly AssistantSessionStateKey < CommonLanguages > SELECTED_TARGET_LANGUAGE_STATE_KEY = new ( nameof ( selectedTargetLanguage ));
private static readonly AssistantSessionStateKey < string > CUSTOM_TARGET_LANGUAGE_STATE_KEY = new ( nameof ( customTargetLanguage ));
private static readonly AssistantSessionStateKey < string > IMPORTANT_ASPECTS_STATE_KEY = new ( nameof ( importantAspects ));
private static readonly AssistantSessionStateKey < bool > USE_CUSTOM_PROMPT_GUIDE_STATE_KEY = new ( nameof ( useCustomPromptGuide ));
private static readonly AssistantSessionStateKey < HashSet < FileAttachment >> CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY = new ( nameof ( customPromptGuideFiles ));
private static readonly AssistantSessionStateKey < string > CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY = new ( nameof ( currentCustomPromptGuidePath ));
private static readonly AssistantSessionStateKey < string > CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY = new ( nameof ( customPromptingGuidelineContent ));
private static readonly AssistantSessionStateKey < bool > IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY = new ( nameof ( isLoadingCustomPromptGuide ));
private static readonly AssistantSessionStateKey < bool > HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY = new ( nameof ( hasUpdatedDefaultRecommendations ));
private static readonly AssistantSessionStateKey < string > OPTIMIZED_PROMPT_STATE_KEY = new ( nameof ( optimizedPrompt ));
private static readonly AssistantSessionStateKey < string > REC_CLARITY_DIRECTNESS_STATE_KEY = new ( nameof ( recClarityDirectness ));
private static readonly AssistantSessionStateKey < string > REC_EXAMPLES_CONTEXT_STATE_KEY = new ( nameof ( recExamplesContext ));
private static readonly AssistantSessionStateKey < string > REC_SEQUENTIAL_STEPS_STATE_KEY = new ( nameof ( recSequentialSteps ));
private static readonly AssistantSessionStateKey < string > REC_STRUCTURE_MARKERS_STATE_KEY = new ( nameof ( recStructureMarkers ));
private static readonly AssistantSessionStateKey < string > REC_ROLE_DEFINITION_STATE_KEY = new ( nameof ( recRoleDefinition ));
private static readonly AssistantSessionStateKey < string > REC_LANGUAGE_CHOICE_STATE_KEY = new ( nameof ( recLanguageChoice ));
/// <inheritdoc />
protected override void CaptureCustomAssistantSessionState ( AssistantSessionStateWriter state )
{
state . Set ( INPUT_PROMPT_STATE_KEY , this . inputPrompt );
state . Set ( SELECTED_TARGET_LANGUAGE_STATE_KEY , this . selectedTargetLanguage );
state . Set ( CUSTOM_TARGET_LANGUAGE_STATE_KEY , this . customTargetLanguage );
state . Set ( IMPORTANT_ASPECTS_STATE_KEY , this . importantAspects );
state . Set ( USE_CUSTOM_PROMPT_GUIDE_STATE_KEY , this . useCustomPromptGuide );
state . SetHashSet ( CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY , this . customPromptGuideFiles );
state . Set ( CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY , this . currentCustomPromptGuidePath );
state . Set ( CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY , this . customPromptingGuidelineContent );
state . Set ( IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY , this . isLoadingCustomPromptGuide );
state . Set ( HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY , this . hasUpdatedDefaultRecommendations );
state . Set ( OPTIMIZED_PROMPT_STATE_KEY , this . optimizedPrompt );
state . Set ( REC_CLARITY_DIRECTNESS_STATE_KEY , this . recClarityDirectness );
state . Set ( REC_EXAMPLES_CONTEXT_STATE_KEY , this . recExamplesContext );
state . Set ( REC_SEQUENTIAL_STEPS_STATE_KEY , this . recSequentialSteps );
state . Set ( REC_STRUCTURE_MARKERS_STATE_KEY , this . recStructureMarkers );
state . Set ( REC_ROLE_DEFINITION_STATE_KEY , this . recRoleDefinition );
state . Set ( REC_LANGUAGE_CHOICE_STATE_KEY , this . recLanguageChoice );
}
/// <inheritdoc />
protected override void RestoreCustomAssistantSessionState ( AssistantSessionStateReader state )
{
state . Restore ( INPUT_PROMPT_STATE_KEY , value => this . inputPrompt = value );
state . Restore ( SELECTED_TARGET_LANGUAGE_STATE_KEY , value => this . selectedTargetLanguage = value );
state . Restore ( CUSTOM_TARGET_LANGUAGE_STATE_KEY , value => this . customTargetLanguage = value );
state . Restore ( IMPORTANT_ASPECTS_STATE_KEY , value => this . importantAspects = value );
state . Restore ( USE_CUSTOM_PROMPT_GUIDE_STATE_KEY , value => this . useCustomPromptGuide = value );
state . RestoreHashSet ( CUSTOM_PROMPT_GUIDE_FILES_STATE_KEY , this . customPromptGuideFiles );
state . Restore ( CURRENT_CUSTOM_PROMPT_GUIDE_PATH_STATE_KEY , value => this . currentCustomPromptGuidePath = value );
state . Restore ( CUSTOM_PROMPTING_GUIDELINE_CONTENT_STATE_KEY , value => this . customPromptingGuidelineContent = value );
state . Restore ( IS_LOADING_CUSTOM_PROMPT_GUIDE_STATE_KEY , value => this . isLoadingCustomPromptGuide = value );
state . Restore ( HAS_UPDATED_DEFAULT_RECOMMENDATIONS_STATE_KEY , value => this . hasUpdatedDefaultRecommendations = value );
state . Restore ( OPTIMIZED_PROMPT_STATE_KEY , value => this . optimizedPrompt = value );
state . Restore ( REC_CLARITY_DIRECTNESS_STATE_KEY , value => this . recClarityDirectness = value );
state . Restore ( REC_EXAMPLES_CONTEXT_STATE_KEY , value => this . recExamplesContext = value );
state . Restore ( REC_SEQUENTIAL_STEPS_STATE_KEY , value => this . recSequentialSteps = value );
state . Restore ( REC_STRUCTURE_MARKERS_STATE_KEY , value => this . recStructureMarkers = value );
state . Restore ( REC_ROLE_DEFINITION_STATE_KEY , value => this . recRoleDefinition = value );
state . Restore ( REC_LANGUAGE_CHOICE_STATE_KEY , value => this . recLanguageChoice = value );
}
2026-04-15 19:40:53 +02:00
private bool ShowUpdatedPromptGuidelinesIndicator => ! this . useCustomPromptGuide && this . hasUpdatedDefaultRecommendations ;
private bool CanPreviewCustomPromptGuide => this . useCustomPromptGuide && this . customPromptGuideFiles . Count > 0 ;
private string CustomPromptGuideFileName => this . customPromptGuideFiles . Count switch
{
0 => T ( "No file selected" ),
_ => this . customPromptGuideFiles . First (). FileName
};
private string? ValidateInputPrompt ( string text )
{
if ( string . IsNullOrWhiteSpace ( text ))
return T ( "Please provide a prompt or prompt description." );
return null ;
}
private string? ValidateCustomLanguage ( string language )
{
if ( this . selectedTargetLanguage == CommonLanguages . OTHER && string . IsNullOrWhiteSpace ( language ))
return T ( "Please provide a custom language." );
return null ;
}
private string SystemPromptLanguage ()
{
var language = this . selectedTargetLanguage switch
{
CommonLanguages . AS_IS => "the source language of the input prompt" ,
CommonLanguages . OTHER => this . customTargetLanguage ,
_ => this . selectedTargetLanguage . Name (),
};
if ( string . IsNullOrWhiteSpace ( language ))
return "the source language of the input prompt" ;
return language ;
}
private async Task OptimizePromptAsync ()
{
2026-04-16 16:48:47 +02:00
await this . Form !. Validate ();
if (! this . InputIsValid )
2026-04-15 19:40:53 +02:00
return ;
this . ClearInputIssues ();
this . ResetOutput ();
this . hasUpdatedDefaultRecommendations = false ;
var promptingGuideline = await this . GetPromptingGuidelineForOptimizationAsync ();
if ( string . IsNullOrWhiteSpace ( promptingGuideline ))
{
if ( this . useCustomPromptGuide )
this . AddInputIssue ( T ( "Please attach and load a valid custom prompt guide file." ));
else
this . AddInputIssue ( T ( "The prompting guideline file could not be loaded. Please verify 'prompting_guideline.md' in Assistants/PromptOptimizer." ));
return ;
}
this . CreateChatThread ();
var requestTime = this . AddUserRequest ( this . BuildOptimizationRequest ( promptingGuideline ), hideContentFromUser : true );
var aiResponse = await this . AddAIResponseAsync ( requestTime , hideContentFromUser : true );
if (! TryParseOptimizationResult ( aiResponse , out var parsedResult ))
{
this . optimizedPrompt = aiResponse . Trim ();
if (! this . useCustomPromptGuide )
{
this . ApplyFallbackRecommendations ();
this . MarkRecommendationsUpdated ();
}
this . AddInputIssue ( T ( "The model response was not in the expected JSON format. The raw response is shown as optimized prompt." ));
this . AddVisibleOptimizedPromptBlock ();
return ;
}
this . ApplyOptimizationResult ( parsedResult );
this . AddVisibleOptimizedPromptBlock ();
}
private string BuildOptimizationRequest ( string promptingGuideline )
{
return
$ $"""
# PROMPTING_GUIDELINE
<GUIDELINE>
{{promptingGuideline}}
</GUIDELINE>
# USER_PROMPT
<USER_PROMPT>
{{this.inputPrompt}}
</USER_PROMPT>
{{this.PromptImportantAspects()}}
""" ;
}
private string PromptImportantAspects ()
{
return string . IsNullOrWhiteSpace ( this . importantAspects ) ? string . Empty : $"""
# IMPORTANT_ASPECTS
<IMPORTANT_ASPECTS>
{this.importantAspects}
</IMPORTANT_ASPECTS>
""" ;
}
private string SystemPromptOutputSchema () =>
"""
{
"optimized_prompt" : "string" ,
"recommendations" : {
"clarity_and_directness" : "string" ,
"examples_and_context" : "string" ,
"sequential_steps" : "string" ,
"structure_with_markers" : "string" ,
"role_definition" : "string" ,
"language_choice" : "string"
}
}
""";
private static bool TryParseOptimizationResult ( string rawResponse , out PromptOptimizationResult parsedResult )
{
parsedResult = new ();
if ( TryDeserialize ( rawResponse , out parsedResult ))
return true ;
var codeFenceMatch = JSON_CODE_FENCE_REGEX . Match ( rawResponse );
if ( codeFenceMatch . Success )
{
var codeFenceJson = codeFenceMatch . Groups [ "json" ]. Value ;
if ( TryDeserialize ( codeFenceJson , out parsedResult ))
return true ;
}
var firstBrace = rawResponse . IndexOf ( '{' );
var lastBrace = rawResponse . LastIndexOf ( '}' );
if ( firstBrace >= 0 && lastBrace > firstBrace )
{
var objectText = rawResponse [ firstBrace ..( lastBrace + 1 )];
if ( TryDeserialize ( objectText , out parsedResult ))
return true ;
}
return false ;
}
private static bool TryDeserialize ( string json , out PromptOptimizationResult parsedResult )
{
parsedResult = new ();
if ( string . IsNullOrWhiteSpace ( json ))
return false ;
try
{
var probe = JsonSerializer . Deserialize < PromptOptimizationResult >( json , JSON_OPTIONS );
if ( probe is null || string . IsNullOrWhiteSpace ( probe . OptimizedPrompt ))
return false ;
parsedResult = probe ;
return true ;
}
catch
{
return false ;
}
}
private void ApplyOptimizationResult ( PromptOptimizationResult optimizationResult )
{
this . optimizedPrompt = optimizationResult . OptimizedPrompt . Trim ();
if ( this . useCustomPromptGuide )
return ;
this . ApplyRecommendations ( optimizationResult . Recommendations );
this . MarkRecommendationsUpdated ();
}
private void MarkRecommendationsUpdated ()
{
this . hasUpdatedDefaultRecommendations = true ;
}
private void ApplyRecommendations ( PromptOptimizationRecommendations recommendations )
{
this . recClarityDirectness = this . EmptyFallback ( recommendations . ClarityAndDirectness );
this . recExamplesContext = this . EmptyFallback ( recommendations . ExamplesAndContext );
this . recSequentialSteps = this . EmptyFallback ( recommendations . SequentialSteps );
this . recStructureMarkers = this . EmptyFallback ( recommendations . StructureWithMarkers );
this . recRoleDefinition = this . EmptyFallback ( recommendations . RoleDefinition );
this . recLanguageChoice = this . EmptyFallback ( recommendations . LanguageChoice );
}
private void ApplyFallbackRecommendations ()
{
this . recClarityDirectness = T ( "Add clearer goals and explicit quality expectations." );
this . recExamplesContext = T ( "Add short examples and background context for your specific use case." );
this . recSequentialSteps = T ( "Break the task into numbered steps if order matters." );
this . recStructureMarkers = T ( "Use headings or markers to separate context, task, and constraints." );
this . recRoleDefinition = T ( "Define a role for the model to focus output style and expertise." );
this . recLanguageChoice = T ( "Use English for complex prompts and explicitly request response language if needed." );
}
private string EmptyFallback ( string text )
{
if ( string . IsNullOrWhiteSpace ( text ))
return T ( "No further recommendation in this area." );
return text . Trim ();
}
private void ResetOutput ()
{
this . optimizedPrompt = string . Empty ;
}
private void ResetGuidelineSummaryToDefault ()
{
this . recClarityDirectness = T ( "Use clear, explicit instructions and directly state quality expectations." );
this . recExamplesContext = T ( "Include short examples and context that explain the purpose behind your requirements." );
this . recSequentialSteps = T ( "Prefer numbered steps when task order matters." );
this . recStructureMarkers = T ( "Separate context, task, constraints, and output format with headings or markers." );
this . recRoleDefinition = T ( "Assign a role to shape tone, expertise, and focus." );
this . recLanguageChoice = T ( "For complex tasks, write prompts in English." );
}
private void AddVisibleOptimizedPromptBlock ()
{
if ( string . IsNullOrWhiteSpace ( this . optimizedPrompt ))
return ;
2026-04-16 16:48:47 +02:00
if ( this . ChatThread is null )
2026-04-15 19:40:53 +02:00
return ;
var visibleResponseContent = new ContentText
{
Text = this . optimizedPrompt ,
};
2026-04-16 16:48:47 +02:00
this . ChatThread . Blocks . Add ( new ContentBlock
2026-04-15 19:40:53 +02:00
{
Time = DateTimeOffset . Now ,
ContentType = ContentType . TEXT ,
Role = ChatRole . AI ,
HideFromUser = false ,
Content = visibleResponseContent ,
});
}
private static async Task < string > ReadPromptingGuidelineAsync ()
{
#if DEBUG
var guidelinePath = Path . Join ( Environment . CurrentDirectory , "Assistants" , "PromptOptimizer" , "prompting_guideline.md" );
return File . Exists ( guidelinePath )
? await File . ReadAllTextAsync ( guidelinePath )
: string . Empty ;
#else
var resourceFileProvider = new ManifestEmbeddedFileProvider ( Assembly . GetAssembly ( type : typeof ( Program ))!, "Assistants/PromptOptimizer" );
var file = resourceFileProvider . GetFileInfo ( "prompting_guideline.md" );
if (! file . Exists )
return string . Empty ;
await using var fileStream = file . CreateReadStream ();
using var reader = new StreamReader ( fileStream );
return await reader . ReadToEndAsync ();
#endif
}
private async Task < string > GetPromptingGuidelineForOptimizationAsync ()
{
if (! this . useCustomPromptGuide )
return await ReadPromptingGuidelineAsync ();
if ( this . customPromptGuideFiles . Count == 0 )
return string . Empty ;
if (! string . IsNullOrWhiteSpace ( this . customPromptingGuidelineContent ))
return this . customPromptingGuidelineContent ;
var fileAttachment = this . customPromptGuideFiles . First ();
await this . LoadCustomPromptGuidelineContentAsync ( fileAttachment );
return this . customPromptingGuidelineContent ;
}
private async Task SetUseCustomPromptGuide ( bool useCustom )
{
this . useCustomPromptGuide = useCustom ;
if (! useCustom )
return ;
if ( this . customPromptGuideFiles . Count == 0 )
return ;
var fileAttachment = this . customPromptGuideFiles . First ();
if ( string . IsNullOrWhiteSpace ( this . customPromptingGuidelineContent ))
await this . LoadCustomPromptGuidelineContentAsync ( fileAttachment );
}
private async Task OnCustomPromptGuideFilesChanged ( HashSet < FileAttachment > files )
{
if ( files . Count == 0 )
{
this . customPromptGuideFiles . Clear ();
this . currentCustomPromptGuidePath = string . Empty ;
this . customPromptingGuidelineContent = string . Empty ;
return ;
}
var selected = files . FirstOrDefault ( file => ! string . Equals ( file . FilePath , this . currentCustomPromptGuidePath , StringComparison . OrdinalIgnoreCase ))
?? files . First ();
var replacedPrevious = ! string . IsNullOrWhiteSpace ( this . currentCustomPromptGuidePath ) &&
! string . Equals ( this . currentCustomPromptGuidePath , selected . FilePath , StringComparison . OrdinalIgnoreCase );
this . customPromptGuideFiles = [ selected ];
this . currentCustomPromptGuidePath = selected . FilePath ;
if ( files . Count > 1 || replacedPrevious )
2026-08-02 21:41:12 +02:00
await this . MessageBus . SendInfo ( new ( Icons . Material . Filled . SwapHoriz , T ( "Replaced the previously selected custom prompt guide file." )));
2026-04-15 19:40:53 +02:00
await this . LoadCustomPromptGuidelineContentAsync ( selected );
}
private async Task LoadCustomPromptGuidelineContentAsync ( FileAttachment fileAttachment )
{
if (! fileAttachment . Exists )
{
this . customPromptingGuidelineContent = string . Empty ;
2026-08-02 21:41:12 +02:00
await this . MessageBus . SendWarning ( new ( Icons . Material . Filled . FindInPage , T ( "The selected custom prompt guide file could not be found." )));
2026-04-15 19:40:53 +02:00
return ;
}
try
{
this . isLoadingCustomPromptGuide = true ;
2026-08-10 16:37:44 +02:00
// A failure was already reported by UserFile.LoadFileData, so we only keep the content:
2026-08-31 08:40:50 +02:00
var extraction = await UserFile . LoadFileData ( fileAttachment . FilePath , this . RustService , this . PandocAvailability );
2026-08-10 16:37:44 +02:00
this . customPromptingGuidelineContent = extraction . HasUsableContent ? extraction . Content : string . Empty ;
2026-04-15 19:40:53 +02:00
}
catch
{
this . customPromptingGuidelineContent = string . Empty ;
2026-08-02 21:41:12 +02:00
await this . MessageBus . SendError ( new ( Icons . Material . Filled . Description , T ( "Failed to load custom prompt guide content." )));
2026-04-15 19:40:53 +02:00
}
finally
{
this . isLoadingCustomPromptGuide = false ;
this . StateHasChanged ();
}
}
private async Task OpenPromptingGuidelineDialog ()
{
var promptingGuideline = await ReadPromptingGuidelineAsync ();
if ( string . IsNullOrWhiteSpace ( promptingGuideline ))
{
2026-08-02 21:41:12 +02:00
await this . MessageBus . SendWarning ( new ( Icons . Material . Filled . MenuBook , T ( "The prompting guideline file could not be loaded." )));
2026-04-15 19:40:53 +02:00
return ;
}
var dialogParameters = new DialogParameters < PromptingGuidelineDialog >
{
{ x => x . GuidelineMarkdown , promptingGuideline }
};
2026-04-16 16:48:47 +02:00
var dialogReference = await this . DialogService . ShowAsync < PromptingGuidelineDialog >( T ( "Prompting Guideline" ), dialogParameters , Dialogs . DialogOptions . FULLSCREEN );
2026-04-15 19:40:53 +02:00
await dialogReference . Result ;
}
private async Task OpenCustomPromptGuideDialog ()
{
if ( this . customPromptGuideFiles . Count == 0 )
return ;
var fileAttachment = this . customPromptGuideFiles . First ();
if ( string . IsNullOrWhiteSpace ( this . customPromptingGuidelineContent ) && ! this . isLoadingCustomPromptGuide )
await this . LoadCustomPromptGuidelineContentAsync ( fileAttachment );
var dialogParameters = new DialogParameters < DocumentCheckDialog >
{
{ x => x . Document , fileAttachment },
{ x => x . FileContent , this . customPromptingGuidelineContent },
};
2026-04-16 16:48:47 +02:00
await this . DialogService . ShowAsync < DocumentCheckDialog >( T ( "Custom Prompt Guide Preview" ), dialogParameters , Dialogs . DialogOptions . FULLSCREEN );
2026-04-15 19:40:53 +02:00
}
}