using AIStudio.Chat;
using AIStudio.Settings.DataModel;
namespace AIStudio.Assistants.BeyondFactsLab;
///
/// Hands the guidelines of an organization for public appearances, see DataBeyondFactsLab.OrganizationRules, to the
/// chats of the steps of the BeyondFacts Lab.
///
///
/// The guidelines inform; they are no instructions for the model. They therefore stay out of the system prompt, where
/// they could outweigh the rules of the lab, and go along with the first message of a chat instead, marked as coming
/// from the organization; the core prompt tells the model how to treat them. Only the request carries them, as it
/// carries the images of the material: the stored chat keeps the first message as it was, and every request carries
/// the guidelines as they are now. A message of their own in front of the first one would not do: two messages of the
/// user in a row break the chat templates of some self-hosted models, and the request has to keep as many messages as
/// the chat, see ContentText.CreateFromProviderAsync.
///
/// The conversation about whether the lab fits a task prepares no appearance, and the research must learn nothing but
/// the public details of the event, so neither of them gets the guidelines. The open points and next steps end with
/// them, see AddTo.
///
public static class BflOrganizationRules
{
///
/// Gets the guidelines which the requests of a chat carry.
///
/// Where the chat belongs.
/// The settings of the lab.
/// The guidelines for a chat of a step, and nothing for every other chat.
public static string For(BflThreadLocation location, DataBeyondFactsLab settings) => location is BflStepThreadLocation ? settings.OrganizationRules : string.Empty;
///
/// Gets the text which the first message of a request carries in front, e.g., to count it beforehand.
///
///
/// German and tagged, like the sections of the prompts.
///
/// The guidelines of the organization; empty without any.
/// The guidelines in their tags, followed by an empty line; nothing without guidelines.
public static string Frame(string rules) => string.IsNullOrWhiteSpace(rules)
? string.Empty
: $"\n{rules}\n\n\n";
///
/// Gives a request the guidelines in front of the first message, without storing them in the chat.
///
///
/// The request gets a copy of that message only; every other message, the answer which streams in above all,
/// stays the same object, and the chat keeps as many messages, see BflConversationService.WithImages.
///
/// The chat.
/// The guidelines of the organization; empty without any.
/// The chat for the request: the chat itself without guidelines or without a message yet.
public static ChatThread WithRules(ChatThread thread, string rules)
{
if (string.IsNullOrWhiteSpace(rules))
return thread;
var index = thread.Blocks.FindIndex(block => block is { Role: ChatRole.USER, Content: ContentText });
if (index < 0)
return thread;
var firstMessage = thread.Blocks[index].DeepClone();
var content = (ContentText)firstMessage.Content!;
content.Text = Frame(rules) + content.Text;
List blocks = [.. thread.Blocks];
blocks[index] = firstMessage;
return thread with { Blocks = blocks };
}
///
/// Adds the guidelines to a result where they belong: the open points and next steps end with them, word for word,
/// under a heading of their own.
///
///
/// Added whenever the result is shown or exported, never stored and never written by the model: so they stay word
/// for word and as they are now, and sharing a session with the research team, which takes the stored texts, leaves
/// them out, since they would reveal the organization.
///
/// The kind of result.
/// The text of the result, as stored.
/// The guidelines of the organization; empty without any.
/// The heading of the guidelines, in the language of the app.
/// The text to show or export.
public static string AddTo(BflResultKind kind, string text, string rules, string heading) => kind is BflResultKind.OPEN_POINTS && !string.IsNullOrWhiteSpace(rules)
? $"{text.TrimEnd()}\n\n## {heading}\n\n{rules}"
: text;
}