using AIStudio.Chat;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
///
/// Shows the system prompt a chat sends, part by part, each under the name of where it comes from.
///
///
/// Nobody types this prompt as it is sent. A chat template replaces the default, a profile, the
/// retrieved data of a data source, and the policy of the selected tools are added to it, and the
/// date goes in front. Each part stands under its own heading, in the order of the prompt, so a
/// person does not have to work out from the text alone where a paragraph came from.
///
/// The parts are those ChatThread.BuildSystemPrompt recorded, so the dialog decides nothing about
/// them. Only the retrieved data needs a word of its own: AI Studio works it out anew whenever a
/// message is sent, see AISrcSelWithRetCtxVal.ProcessAsync. What the dialog shows of it was sent with
/// the last message, and the next one gets what the search finds then. Before the first search,
/// e.g., in a new chat or one just loaded, there is nothing to show, but the chat may still search.
/// Two more things stand where they would be: a selected profile which the chat template does not
/// allow, and the instruction the tool calling loop adds once the limit of tool calls is reached.
///
public partial class SystemPromptDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Inject]
private RustService RustService { get; init; } = null!;
///
/// The system prompt, together with the parts it was assembled from.
///
[Parameter]
public PreparedSystemPrompt Prompt { get; set; } = null!;
///
/// The display names of the tools whose usage policy is part of the prompt.
///
[Parameter]
public IReadOnlyList ToolNames { get; set; } = [];
///
/// How the next message searches the data sources of the chat, or null when it searches none.
///
[Parameter]
public DataSourceRetrievalMode? RetrievalMode { get; set; }
private IReadOnlyList sections = [];
#region Overrides of ComponentBase
protected override void OnParametersSet()
{
this.sections = this.BuildSections();
base.OnParametersSet();
}
#endregion
///
/// Lists the parts of the prompt in its order, with what stands where nothing was added.
///
///
/// Retrieved data follows the base prompt or the chat template, and a profile follows the
/// retrieved data. So a chat which has not retrieved anything yet, but searches its data sources,
/// says so right after the base prompt, and a profile which the chat template switched off is
/// named where it would otherwise stand.
///
private IReadOnlyList BuildSections()
{
var result = new List(this.Prompt.Parts.Count + 2);
var hasRetrievedData = this.Prompt.Parts.Any(part => part.Kind is SystemPromptPartKind.RETRIEVED_DATA);
foreach (var part in this.Prompt.Parts)
{
result.Add(this.SectionOf(part));
switch (part.Kind)
{
case SystemPromptPartKind.BASE_PROMPT or SystemPromptPartKind.CHAT_TEMPLATE when !hasRetrievedData:
if (this.NoticeWithoutRetrievedData is { Length: > 0 } notice)
result.Add(new(T("Data from your data sources"), string.Empty, notice));
this.AddSwitchedOffProfile(result);
break;
case SystemPromptPartKind.RETRIEVED_DATA:
this.AddSwitchedOffProfile(result);
break;
}
}
return result;
}
private Section SectionOf(SystemPromptPart part) => part.Kind switch
{
SystemPromptPartKind.DATE_TIME => new(T("Current date and time"), part.Text, string.Empty),
SystemPromptPartKind.BASE_PROMPT when part.Text == SystemPrompts.DEFAULT => new(T("Default prompt of AI Studio"), part.Text, string.Empty),
SystemPromptPartKind.BASE_PROMPT => new(T("System prompt of this chat"), part.Text, string.Empty),
SystemPromptPartKind.CHAT_TEMPLATE => new(string.Format(T("Chat template '{0}'"), part.SourceName), part.Text, string.Empty),
SystemPromptPartKind.RETRIEVED_DATA => new(T("Data from your data sources"), part.Text, this.NoticeWithRetrievedData),
SystemPromptPartKind.PROFILE => new(string.Format(T("Profile '{0}'"), part.SourceName), part.Text, string.Empty),
SystemPromptPartKind.TOOL_INSTRUCTIONS => new(string.Format(T("Instructions for your tools: {0}"), string.Join(", ", this.ToolNames)), part.Text, T("When the AI reaches the limit of tool calls for one answer, AI Studio adds one more instruction at the end: to answer with what the tools have returned so far.")),
_ => new(string.Empty, part.Text, string.Empty),
};
private void AddSwitchedOffProfile(List result)
{
if (this.Prompt.SwitchedOffProfile == Profile.NO_PROFILE)
return;
result.Add(new(string.Format(T("Profile '{0}'"), this.Prompt.SwitchedOffProfile.Name), string.Empty, T("The chat template does not allow profiles, so your selected profile is not part of the prompt.")));
}
private string NoticeWithRetrievedData => this.RetrievalMode is DataSourceRetrievalMode.EVERY_MESSAGE
? T("AI Studio retrieved this data for your last message. When you send your next message, AI Studio searches your data sources again and replaces this data with what it finds then.")
: T("AI Studio sent this data with your last message. When you send your next message, AI Studio works out this part anew.");
private string NoticeWithoutRetrievedData => this.RetrievalMode switch
{
DataSourceRetrievalMode.EVERY_MESSAGE => T("When you send your next message, AI Studio searches your data sources and adds what it finds here."),
DataSourceRetrievalMode.SEMANTIC_SEARCH => T("The AI searches your data sources itself whenever a question calls for it. What it finds reaches the AI as the result of that search, not as part of this prompt."),
_ => string.Empty,
};
private async Task CopyCompletePrompt() => await this.RustService.CopyText2Clipboard(this.Prompt.Text);
private void Close() => this.MudDialog.Close();
///
/// One part of the prompt as the dialog shows it.
///
/// Where the part comes from.
/// The text of the part, or empty when only the notice stands there.
/// What the user should know about the part, or empty when there is nothing to say.
private sealed record Section(string Heading, string Text, string Notice);
}