using System.Text;
namespace AIStudio.Assistants.BeyondFactsLab;
///
/// Puts a session of the BeyondFacts Lab together for its research team: how the person worked with the lab, step by
/// step, without what would tell who the person is.
///
///
///
/// A session holds the details of every step, its confirmed work state, its conversation as the person saw it, and the
/// confirmed materials the lab wrote. It leaves out everything the research team does not need and which would point
/// to the person: the name of the preparation, the content and the file names of the material, the texts of the
/// research on the web, which describe the event, the fields which pin down the event, drafts, and hidden messages. The
/// reflection folder stays out as well, since it only repeats the confirmed work states. AI Studio keeps no profile
/// and no path in a session, so none of them appears either. Personal disclosures stay out unless the person includes
/// them, and so does the conversation of a step which may contain some.
///
///
/// No model takes part, and the session is not anonymized yet: BflAnonymizer replaces the names and the other personal
/// details in it afterward. The texts therefore stay as they were written, without Markdown escapes, which would hide
/// an address or a phone number from the anonymizer. Every line ends with LF, on every system.
///
///
public static class BflShareModelBuilder
{
private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflShareModelBuilder).Namespace, nameof(BflShareModelBuilder));
///
/// Puts the session together.
///
/// What the session is put together from.
/// The session, in Markdown.
public static string Build(BflShareInput input)
{
var text = new StringBuilder();
text.Append("# ").AppendLineLF(TB("BeyondFacts Lab: a shared session"));
AppendAbout(text, input);
foreach (var step in BflStep.All)
AppendStep(text, step, input);
return text.ToString().TrimEnd() + "\n";
}
///
/// Appends what the research team needs to read the session: the version of AI Studio, the date, how much material
/// the person added, and on which topics the lab researched.
///
/// The session so far.
/// What the session is put together from.
private static void AppendAbout(StringBuilder text, BflShareInput input)
{
var documents = input.Materials.Count(material => material.Kind is BflMaterialKind.DOCUMENT);
var recordings = input.Materials.Count(material => material.Kind is BflMaterialKind.RECORDING);
var images = input.Materials.Count(material => material.Kind is BflMaterialKind.IMAGE);
var topics = BflResearchTopic.All.Where(input.ResearchTopics.Contains).Select(topic => topic.Title).ToList();
text.AppendLineLF().Append("## ").AppendLineLF(TB("About this session")).AppendLineLF();
AppendItem(text, TB("Version of AI Studio"), input.AppVersion);
AppendItem(text, TB("Shared on"), input.SharedAtUtc.ToLocalTime().ToString("d", input.Culture));
AppendItem(text, TB("Material"), string.Format(input.Culture, TB("{0} documents, {1} recordings, {2} images"), documents, recordings, images));
AppendItem(text, TB("Research on the web"), topics.Count > 0 ? string.Join(", ", topics) : TB("none"));
AppendItem(text, TB("Personal disclosures"), input.IncludePersonalDisclosures ? TB("included") : TB("left out"));
}
///
/// Appends a step: its details, its confirmed work state, its conversation, and for the last step the confirmed
/// materials.
///
/// The session so far.
/// The step.
/// What the session is put together from.
private static void AppendStep(StringBuilder text, BflStep step, BflShareInput input)
{
text.AppendLineLF().Append("## ").AppendLineLF(step.Title);
AppendDetails(text, step, input);
AppendWorkState(text, step, input);
AppendConversation(text, step, input);
if (step is BflStep.RESULTS)
AppendResults(text, input);
}
///
/// Appends the details of a step which the person entered, with the labels of the fields and options.
///
/// The session so far.
/// The step.
/// What the session is put together from.
private static void AppendDetails(StringBuilder text, BflStep step, BflShareInput input)
{
text.AppendLineLF().Append("### ").AppendLineLF(TB("Details")).AppendLineLF();
var form = input.Forms.GetValueOrDefault(step);
var any = false;
foreach (var field in BflFieldCatalog.ForStep(step))
{
if (form is null || field.IdentifiesEvent || field.IsPersonalDisclosure && !input.IncludePersonalDisclosures)
continue;
var value = ValueOf(field, form);
if (string.IsNullOrWhiteSpace(value))
continue;
any = true;
if (field.Kind is BflFieldKind.MULTI_LINE)
text.Append("**").Append(field.Label).AppendLineLF(":**").AppendLineLF().AppendLineLF(WithLineBreaks(value)).AppendLineLF();
else
AppendItem(text, field.Label, value);
}
if (!any)
text.Append('_').Append(TB("No details.")).AppendLineLF("_");
}
///
/// Appends the confirmed work state of a step, with all of its sections.
///
/// The session so far.
/// The step.
/// What the session is put together from.
private static void AppendWorkState(StringBuilder text, BflStep step, BflShareInput input)
{
text.AppendLineLF().Append("### ").AppendLineLF(TB("Confirmed work state")).AppendLineLF();
if (input.WorkStates.GetValueOrDefault(step)?.Confirmed is not { } confirmed)
{
text.Append('_').Append(TB("No confirmed work state.")).AppendLineLF("_");
return;
}
var any = false;
foreach (var section in BflWorkStateSections.ForStep(step))
{
if (section.IsPersonalDisclosure && !input.IncludePersonalDisclosures || !confirmed.Sections.TryGetValue(section.Key, out var sectionText) || string.IsNullOrWhiteSpace(sectionText))
continue;
any = true;
text.Append("#### ").AppendLineLF(section.Title).AppendLineLF().AppendLineLF(WithLineBreaks(sectionText)).AppendLineLF();
}
if (!any)
text.Append('_').Append(TB("The confirmed work state holds nothing to share.")).AppendLineLF("_");
}
///
/// Appends the conversation of a step as the person saw it.
///
/// The session so far.
/// The step.
/// What the session is put together from.
private static void AppendConversation(StringBuilder text, BflStep step, BflShareInput input)
{
text.AppendLineLF().Append("### ").AppendLineLF(TB("Conversation")).AppendLineLF();
if (!input.Conversations.TryGetValue(step, out var messages) || messages.Count is 0)
{
text.Append('_').Append(TB("No conversation.")).AppendLineLF("_");
return;
}
if (!input.IncludePersonalDisclosures && BflReflectionFolder.MayHoldPersonalDisclosures(step))
{
text.Append('_').Append(TB("Left out, as it may contain personal disclosures.")).AppendLineLF("_");
return;
}
foreach (var message in messages)
{
var speaker = message.IsFromPerson ? TB("Person") : TB("Lab");
text.Append("**").Append(speaker).AppendLineLF(":**").AppendLineLF().AppendLineLF(WithLineBreaks(message.Text)).AppendLineLF();
}
}
///
/// Appends the confirmed versions of the materials the lab wrote. The reflection folder stays out, since it only
/// repeats the confirmed work states.
///
/// The session so far.
/// What the session is put together from.
private static void AppendResults(StringBuilder text, BflShareInput input)
{
foreach (var kind in BflResultKind.All.Where(kind => kind.IsWrittenByModel))
{
text.AppendLineLF().Append("### ").AppendLineLF(kind.Title).AppendLineLF();
if (input.Results.GetValueOrDefault(kind)?.Confirmed is { } confirmed && !string.IsNullOrWhiteSpace(confirmed.Text))
text.AppendLineLF(WithLineBreaks(confirmed.Text));
else
text.Append('_').Append(TB("Not confirmed.")).AppendLineLF("_");
}
}
///
/// Appends a line of a list with a label in bold.
///
/// The session so far.
/// The label.
/// The value, in one line.
private static void AppendItem(StringBuilder text, string label, string value) => text.Append("- **").Append(label).Append(":** ").AppendLineLF(value.Trim().ReplaceLineEndings(" "));
///
/// Gets the value of a field as the person sees it: the labels of the options chosen, or the text entered.
///
/// The field.
/// The form of the step of the field.
/// The value, empty when the field has none or only an option this version does not know.
private static string ValueOf(BflFieldDefinition field, BflStepFormData form) => field.Kind switch
{
BflFieldKind.CHOICE => field.Options.FirstOrDefault(option => option.Id == form.Get(field.Id))?.Label ?? string.Empty,
BflFieldKind.MULTI_CHOICE => string.Join(", ", field.Options.Where(option => form.GetChoices(field.Id).Contains(option.Id)).Select(option => option.Label)),
_ => form.Get(field.Id),
};
///
/// Gets a text which the person or a model wrote, trimmed and with LF line breaks.
///
/// The text.
private static string WithLineBreaks(string text) => text.Trim().ReplaceLineEndings("\n");
}