diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabJsonContext.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabJsonContext.cs index f84a96c8b..07792aaed 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabJsonContext.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabJsonContext.cs @@ -13,6 +13,9 @@ namespace AIStudio.Assistants.BeyondFactsLab; [JsonSerializable(typeof(BflResult))] [JsonSerializable(typeof(BflResearch))] +// The value deck, which comes with the app: +[JsonSerializable(typeof(BflValueDeck))] + // The preparation selected last: [JsonSerializable(typeof(Guid?))] internal sealed partial class BeyondFactsLabJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabStore.Forms.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabStore.Forms.cs index d3e99d732..8727e329e 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabStore.Forms.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BeyondFactsLabStore.Forms.cs @@ -113,12 +113,18 @@ public sealed partial class BeyondFactsLabStore throw new InvalidDataException("This version of AI Studio cannot open the form.", exception); } - if (form is null || form.SchemaVersion is < 1 or > BflStepFormData.CURRENT_SCHEMA_VERSION || IsNull(form.Values)) + if (form is null || form.SchemaVersion is < 1 or > BflStepFormData.CURRENT_SCHEMA_VERSION || IsNull(form.Values) || form.CardSort is { } cardSort && IsBroken(cardSort)) throw new InvalidDataException("This version of AI Studio cannot open the form."); return form; } + /// + /// Tells whether a stored card sort lacks one of its parts, which JSON can make null. + /// + /// The card sort. + private static bool IsBroken(BflCardSort cardSort) => IsNull(cardSort.Categories) || IsNull(cardSort.OwnValues) || IsNull(cardSort.Focus) || cardSort.Focus.Any(IsNull) || cardSort.OwnValues.Any(value => IsNull(value) || IsNull(value.Id) || IsNull(value.Name) || IsNull(value.Definition)); + /// /// Gets the file of the form of a step. /// diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflCardSort.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflCardSort.cs new file mode 100644 index 000000000..2c2e47151 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflCardSort.cs @@ -0,0 +1,176 @@ +using System.Text.Json.Serialization; + +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// How the person sorted the values of the deck in the step "Values and perspectives" of the BeyondFacts Lab: a +/// category for each value, the values the deck misses, and the two or three values to look at more closely. +/// +/// +/// The card sort belongs to the details of its step and is stored with them, see BflStepFormData.CardSort. Every part +/// is voluntary and stays changeable: a value without a category stays open, and the step can be finished without +/// any sorting. The methods keep the limits. A category or a choice whose value the deck does not know, e.g., one of +/// another version of the deck, stays stored but shows nowhere, see FocusIn. +/// +public sealed class BflCardSort +{ + /// + /// How many values of their own a person may add. + /// + public const int MAX_OWN_VALUES = 10; + + /// + /// How many characters the name of an own value may have. + /// + public const int MAX_NAME_LENGTH = 60; + + /// + /// How many characters the definition of an own value may have. + /// + public const int MAX_DEFINITION_LENGTH = 300; + + /// + /// How many values a person may choose to look at more closely: two or three, after Lara Wüster's dialogue logic. + /// + public const int MAX_FOCUS = 3; + + /// + /// Gets or sets the category of each value the person sorted, by the ID of the value in the deck. + /// + public Dictionary Categories { get; set; } = new(StringComparer.Ordinal); + + /// + /// Gets or sets the values the person added, in the order they added them. + /// + public List OwnValues { get; set; } = []; + + /// + /// Gets or sets the IDs of the values the person wants to look at more closely, of the deck or their own, in the + /// order they chose them. The order is no ranking. + /// + public List Focus { get; set; } = []; + + /// + /// Gets whether the person has neither sorted a value, nor added one, nor chosen one to look at more closely. + /// + [JsonIgnore] + public bool IsEmpty => this.Categories.Values.All(category => category is BflValueCategory.NONE) && this.OwnValues.Count is 0 && this.Focus.Count is 0; + + /// + /// Gets whether the person may add another value of their own. + /// + [JsonIgnore] + public bool CanAddOwnValue => this.OwnValues.Count < MAX_OWN_VALUES; + + /// + /// Gets the category of a value of the deck. + /// + /// The ID of the value. + /// The category, or NONE when the person has not sorted the value. + public BflValueCategory CategoryOf(string valueId) => this.Categories.GetValueOrDefault(valueId, BflValueCategory.NONE); + + /// + /// Sorts a value of the deck into a category; NONE leaves it open again. + /// + /// The ID of the value. + /// The category. + public void Sort(string valueId, BflValueCategory category) + { + if (category is BflValueCategory.NONE) + this.Categories.Remove(valueId); + else + this.Categories[valueId] = category; + } + + /// + /// Adds a value which the deck misses. + /// + /// The name of the value; spaces around it do not count, and a longer name is cut. + /// What the value means to the person; it may be empty, and a longer one is cut. + /// The value, or null when the name is empty or the person has added as many values as they may. + public BflOwnValue? AddOwnValue(string name, string definition) + { + var limitedName = Limit(name, MAX_NAME_LENGTH); + if (limitedName.Length is 0 || !this.CanAddOwnValue) + return null; + + var value = new BflOwnValue { Id = Guid.NewGuid().ToString("N"), Name = limitedName, Definition = Limit(definition, MAX_DEFINITION_LENGTH) }; + this.OwnValues.Add(value); + return value; + } + + /// + /// Changes the name and the definition of a value of the person. + /// + /// The ID of the value. + /// The new name; spaces around it do not count, and a longer name is cut. + /// The new definition; it may be empty, and a longer one is cut. + /// True when the value changed; false when the person has no such value or the name is empty. + public bool ChangeOwnValue(string valueId, string name, string definition) + { + var limitedName = Limit(name, MAX_NAME_LENGTH); + if (limitedName.Length is 0 || this.OwnValues.FirstOrDefault(candidate => string.Equals(candidate.Id, valueId, StringComparison.Ordinal)) is not { } value) + return false; + + value.Name = limitedName; + value.Definition = Limit(definition, MAX_DEFINITION_LENGTH); + return true; + } + + /// + /// Removes a value of the person, also from the values to look at more closely. + /// + /// The ID of the value. + public void RemoveOwnValue(string valueId) + { + this.OwnValues.RemoveAll(candidate => string.Equals(candidate.Id, valueId, StringComparison.Ordinal)); + this.Focus.RemoveAll(candidate => string.Equals(candidate, valueId, StringComparison.Ordinal)); + } + + /// + /// Chooses the values to look at more closely. The values chosen before keep their place; of the new ones, only + /// as many are taken as fit within MAX_FOCUS. + /// + /// The IDs of all values the person wants to look at more closely. + public void ChooseFocus(IReadOnlyCollection valueIds) + { + var focus = this.Focus.Where(valueIds.Contains).Distinct(StringComparer.Ordinal).ToList(); + foreach (var valueId in valueIds) + { + if (focus.Count >= MAX_FOCUS) + break; + + if (!focus.Contains(valueId, StringComparer.Ordinal)) + focus.Add(valueId); + } + + this.Focus = focus; + } + + /// + /// Gets the values to look at more closely which exist, in the deck or among the values of the person, at most + /// MAX_FOCUS of them in the order the person chose them. + /// + /// The deck the person sorted. + /// The IDs of the values. + public IReadOnlyList FocusIn(BflValueDeck deck) => [.. this.Focus.Distinct(StringComparer.Ordinal).Where(valueId => this.NameOf(valueId, deck) is not null).Take(MAX_FOCUS)]; + + /// + /// Gets the name of a value, of the deck or of the person. + /// + /// The ID of the value. + /// The deck the person sorted. + /// The name, or null when neither the deck nor the person has such a value. + public string? NameOf(string valueId, BflValueDeck deck) => deck.Find(valueId)?.Name ?? this.OwnValues.FirstOrDefault(candidate => string.Equals(candidate.Id, valueId, StringComparison.Ordinal))?.Name; + + /// + /// Trims a text and cuts it to a number of characters. + /// + /// The text. + /// The number of characters. + private static string Limit(string? text, int maxLength) + { + var trimmed = (text ?? string.Empty).Trim(); + return trimmed.Length > maxLength ? trimmed[..maxLength].TrimEnd() : trimmed; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflConversationService.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflConversationService.cs index 7b3ed5706..51364b2bd 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflConversationService.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflConversationService.cs @@ -152,8 +152,8 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh /// Where the chat belongs. /// The provider which answers; its model decides how much of the material fits. /// The text to count. - /// The app comes without one of the prompts. - /// A prompt has a placeholder without a value. + /// The app comes without one of the prompts or without the value deck. + /// A prompt has a placeholder without a value, or something is wrong with the value deck. public async Task ComposeTextForCountAsync(BflThreadLocation location, AIStudio.Settings.Provider provider) { var requestMaterial = await this.ComposeRequestAsync(location, provider); @@ -175,8 +175,8 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh /// Where the chat belongs. /// The provider which answers; its model decides how much of the material fits. /// The system prompt, the images, and the guidelines. - /// The app comes without one of the prompts. - /// A prompt has a placeholder without a value. + /// The app comes without one of the prompts or without the value deck. + /// A prompt has a placeholder without a value, or something is wrong with the value deck. internal async Task ComposeRequestAsync(BflThreadLocation location, AIStudio.Settings.Provider provider) { var addressForm = Tools.PluginSystem.I18N.I.AddressForm; @@ -190,9 +190,10 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh var workStates = await store.LoadWorkStatesAsync(stepLocation.ProjectId); var parts = await BflPromptLibrary.LoadPartsAsync(stepLocation.Step, addressForm, this.PromptOverridesDirectory); var research = await this.LoadResearchAsync(stepLocation); - var shares = await this.ShareMaterialsAsync(stepLocation, parts, forms, workStates, tools, research, rules, language, provider); + var valueDeck = await LoadValueDeckAsync(stepLocation); + var shares = await this.ShareMaterialsAsync(stepLocation, parts, forms, workStates, tools, research, rules, valueDeck, language, provider); var materials = await this.ReadMaterialsAsync(stepLocation.ProjectId, shares); - return new(BflPromptComposer.Compose(parts, stepLocation.Step, forms, materials, language, workStates, tools, research, rules), this.ImagesOf(stepLocation.ProjectId, shares), rules); + return new(BflPromptComposer.Compose(parts, stepLocation.Step, forms, materials, language, workStates, tools, research, rules, valueDeck), this.ImagesOf(stepLocation.ProjectId, shares), rules); case BflOnboardingThreadLocation: // It never gets the guidelines themselves, but it tells the person that they exist: @@ -299,8 +300,8 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh /// The step. /// The provider which answers; its model decides how much of the material fits. /// The share of each material this version can open, in the order the person added them. - /// The app comes without one of the prompts. - /// A prompt has a placeholder without a value. + /// The app comes without one of the prompts or without the value deck. + /// A prompt has a placeholder without a value, or something is wrong with the value deck. public async Task> ShareMaterialsAsync(BflStepThreadLocation location, AIStudio.Settings.Provider provider) { var language = (await settingsManager.GetActiveLanguagePlugin()).LangName; @@ -310,7 +311,7 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh var tools = await BflResearchAvailability.CheckAsync(toolRegistry, provider); var research = await this.LoadResearchAsync(location); var rules = BflOrganizationRules.For(location, settingsManager.ConfigurationData.BeyondFactsLab); - return await this.ShareMaterialsAsync(location, parts, forms, workStates, tools, research, rules, language, provider); + return await this.ShareMaterialsAsync(location, parts, forms, workStates, tools, research, rules, await LoadValueDeckAsync(location), language, provider); } /// @@ -322,6 +323,18 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh ? await store.LoadAllResearchAsync(location.ProjectId) : new Dictionary(); + /// + /// Loads the value deck a step carries in its prompt, with the card sort of the person: the step "Values and + /// perspectives" the deck in the language of the app, every other step none. + /// + /// The step. + /// The deck, or null for every other step. + /// The app comes without the deck. + /// Something is wrong with the deck. + private static async Task LoadValueDeckAsync(BflStepThreadLocation location) => location.Step is BflStep.VALUES_AND_PERSPECTIVES + ? await BflValueDeck.LoadAsync(Tools.PluginSystem.I18N.I.Culture) + : null; + /// /// Decides how much of the material of a preparation a request of a step carries: which images go along, and how /// much of each text fits beside them. @@ -333,10 +346,11 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh /// What keeps the tools of the lab from being offered, which the prompt describes as well. /// The research the prompt of the step carries, by its topics. /// The guidelines of the organization, which take room in the prompt and in the first message. + /// The value deck with which the prompt of the step shows the card sort, which takes room as well; null for the other steps. /// The name of the language the model answers in. /// The provider which answers. /// The share of each material this version can open, in the order the person added them. - private async Task> ShareMaterialsAsync(BflStepThreadLocation location, BflPromptParts parts, IReadOnlyDictionary forms, IReadOnlyDictionary workStates, BflResearchAvailability tools, IReadOnlyDictionary research, string organizationRules, string language, AIStudio.Settings.Provider provider) + private async Task> ShareMaterialsAsync(BflStepThreadLocation location, BflPromptParts parts, IReadOnlyDictionary forms, IReadOnlyDictionary workStates, BflResearchAvailability tools, IReadOnlyDictionary research, string organizationRules, BflValueDeck? valueDeck, string language, AIStudio.Settings.Provider provider) { var materials = (await store.ListMaterialsAsync(location.ProjectId)).Select(entry => entry.Material).OfType().ToList(); if (materials.Count is 0) @@ -345,7 +359,7 @@ public sealed class BflConversationService(BeyondFactsLabStore store, EmbeddedCh var imageShares = BflMaterialBudget.ShareImages([.. materials.Where(material => material.Kind is BflMaterialKind.IMAGE)], ImagesTheModelTakes(provider)); var sentImages = imageShares.Count(share => share.Image is BflImageDelivery.SENT); - var promptWithoutMaterials = BflPromptComposer.Compose(parts, location.Step, forms, [], language, workStates, tools, research, organizationRules); + var promptWithoutMaterials = BflPromptComposer.Compose(parts, location.Step, forms, [], language, workStates, tools, research, organizationRules, valueDeck); var characters = BflMaterialBudget.CharactersFor(provider.GetModelProfile().Context, promptWithoutMaterials.Length + BflOrganizationRules.Frame(organizationRules).Length, sentImages); var textShares = BflMaterialBudget.Share([.. materials.Where(material => material.Kind is not BflMaterialKind.IMAGE)], characters); diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldCatalog.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldCatalog.cs index 0c76149c4..13a967c0a 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldCatalog.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldCatalog.cs @@ -109,9 +109,9 @@ public static class BflFieldCatalog Options = [new(GOAL_INFORM), new(GOAL_MODERATE), new(GOAL_TAKE_POSITION), new(GOAL_RAISE_AWARENESS), new(GOAL_SHOW_OPTIONS), new(GOAL_INFLUENCE_POLICY)], }, - new(VALUES_AND_CONCERNS, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE), - new(PERSONAL_CONCERN, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE) { IsPersonalDisclosure = true }, - new(OTHER_PERSPECTIVES, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE), + new(VALUES_AND_CONCERNS, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE) { Section = BflFormSection.WHAT_MATTERS_TO_ME }, + new(PERSONAL_CONCERN, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE) { IsPersonalDisclosure = true, Section = BflFormSection.WHAT_TOUCHES_ME }, + new(OTHER_PERSPECTIVES, BflStep.VALUES_AND_PERSPECTIVES, BflFieldKind.MULTI_LINE) { Section = BflFormSection.WHAT_MOVES_OTHERS }, new(COMMUNICATION_WORRIES, BflStep.STRATEGY, BflFieldKind.MULTI_LINE), diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldDefinition.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldDefinition.cs index f49e60196..197ddf5db 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldDefinition.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFieldDefinition.cs @@ -42,6 +42,11 @@ public sealed record BflFieldDefinition(string Id, BflStep Step, BflFieldKind Ki /// public BflNameRole NameRole { get; init; } = BflNameRole.NONE; + /// + /// Gets the section of the form which shows the field under its heading. + /// + public BflFormSection Section { get; init; } = BflFormSection.NONE; + /// /// Gets the options of a field which offers a choice, in the order to show them. /// diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSection.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSection.cs new file mode 100644 index 000000000..27baa952f --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSection.cs @@ -0,0 +1,31 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// A section of a form "Your details" of the BeyondFacts Lab, which groups fields under a heading. +/// +/// +/// The step "Values and perspectives" follows the three sections of Lara Wüster's dialogue logic. The forms of the +/// other steps have no sections. +/// +public enum BflFormSection +{ + /// + /// The field stands in the form without a section. + /// + NONE, + + /// + /// What matters to the person, with the card sort. + /// + WHAT_MATTERS_TO_ME, + + /// + /// What touches the person personally. + /// + WHAT_TOUCHES_ME, + + /// + /// What moves others, whose points of view the person has hardly considered so far. + /// + WHAT_MOVES_OTHERS, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSectionExtensions.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSectionExtensions.cs new file mode 100644 index 000000000..ca3f1c39b --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflFormSectionExtensions.cs @@ -0,0 +1,27 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +public static class BflFormSectionExtensions +{ + private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflFormSectionExtensions).Namespace, nameof(BflFormSectionExtensions)); + + /// The section. + extension(BflFormSection section) + { + /// + /// Gets the heading of the section, in the words of the person; empty for fields without a section. + /// + public string Title => section switch + { + BflFormSection.WHAT_MATTERS_TO_ME => TB("What matters to me"), + BflFormSection.WHAT_TOUCHES_ME => TB("What touches me"), + BflFormSection.WHAT_MOVES_OTHERS => TB("What moves others"), + + _ => string.Empty, + }; + + /// + /// Gets whether the section holds the card sort, above its fields. + /// + public bool HasCardSort => section is BflFormSection.WHAT_MATTERS_TO_ME; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflIntroduction.razor b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflIntroduction.razor index be11c2872..0160ce632 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflIntroduction.razor +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflIntroduction.razor @@ -45,6 +45,11 @@ +@T("Value card sort") + + @string.Format(Culture, TA("In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality."), BflStep.VALUES_AND_PERSPECTIVES.Title) + + @T("Research on the web") @string.Format(Culture, TA("In the step '{0}', research agents of the lab look into the event, its participants, and the public debate on its topic, one agent for each. They start on their own once you confirm the event details, provided the web search is set up in AI Studio, and you can start them again at any time."), BflStep.STRATEGY.Title) diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflOwnValue.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflOwnValue.cs new file mode 100644 index 000000000..eccdb1ec2 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflOwnValue.cs @@ -0,0 +1,23 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// A value which the person adds to the card sort of the BeyondFacts Lab because the deck misses it, with what it +/// means to them. +/// +public sealed class BflOwnValue +{ + /// + /// Gets or sets the ID of the value, which the choice of values to look at more closely refers to. It never changes. + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the name of the value. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets what the value means to the person; it may be empty. + /// + public string Definition { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflPromptComposer.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflPromptComposer.cs index 22d805f4c..cad1c32b9 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflPromptComposer.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflPromptComposer.cs @@ -38,9 +38,10 @@ public static class BflPromptComposer /// What keeps the tools of the lab from being offered with the provider of the preparation; null counts as no tools at all. /// The research of the preparation, by its topics; only the step "Strategy" gets it. Null when there is none. /// The guidelines of the organization, which the first message carries, see BflOrganizationRules; only while there are any, the prompt says how to treat them. Null when there are none. + /// The value deck in the language of the app, with which the step "Values and perspectives" shows the card sort of the person among its details; the other steps leave it out. Null when there is none. /// The system prompt. /// The core prompt has a placeholder without a value. - public static string Compose(BflPromptParts parts, BflStep step, IReadOnlyDictionary forms, IReadOnlyList materials, string responseLanguage, IReadOnlyDictionary? workStates = null, BflResearchAvailability? tools = null, IReadOnlyDictionary? research = null, string? organizationRules = null) => BflPromptTemplate.Fill(parts.Core, new Dictionary(StringComparer.Ordinal) + public static string Compose(BflPromptParts parts, BflStep step, IReadOnlyDictionary forms, IReadOnlyList materials, string responseLanguage, IReadOnlyDictionary? workStates = null, BflResearchAvailability? tools = null, IReadOnlyDictionary? research = null, string? organizationRules = null, BflValueDeck? valueDeck = null) => BflPromptTemplate.Fill(parts.Core, new Dictionary(StringComparer.Ordinal) { [BflPromptTemplate.RESPONSE_LANGUAGE] = responseLanguage, [BflPromptTemplate.ADDRESS_FORM] = parts.Address.Trim(), @@ -50,7 +51,7 @@ public static class BflPromptComposer [BflPromptTemplate.TOOLS] = BflToolStatus.ForStep(tools ?? NO_TOOLS).Trim(), [BflPromptTemplate.RESEARCH] = Research(step, research ?? new Dictionary(), forms), [BflPromptTemplate.WORK_STATES] = WorkStates(step, workStates ?? new Dictionary(), forms), - [BflPromptTemplate.DETAILS] = Details(step, forms), + [BflPromptTemplate.DETAILS] = Details(step, forms, valueDeck), [BflPromptTemplate.MISSING_DETAILS] = MissingDetails(step, forms), [BflPromptTemplate.ORGANIZATION_RULES] = OnlyWithRules(parts.OrganizationRules, organizationRules) }).ReplaceLineEndings(LINE_BREAK.ToString()); @@ -397,11 +398,14 @@ public static class BflPromptComposer /// /// /// Something the person confided, such as their personal involvement, reaches only the chat of its own step: - /// no other step may confront them with it by surprise. + /// no other step may confront them with it by surprise. The card sort reaches only the chat of its own step as + /// well: the later steps learn what the person took from it through the confirmed work state, after Lara Wüster's + /// rule that only what the person confirmed goes on into the preparation. /// /// The step the chat belongs to. /// The forms of the preparation, by their steps. - private static string Details(BflStep step, IReadOnlyDictionary forms) + /// The value deck in the language of the app, or null when there is none. + private static string Details(BflStep step, IReadOnlyDictionary forms, BflValueDeck? valueDeck) { var text = new StringBuilder(); foreach (var candidate in BflStep.All) @@ -423,6 +427,9 @@ public static class BflPromptComposer lines.Add($"- {field.Label}{marker}: {value.Replace("\n", "\n ", StringComparison.Ordinal)}"); } + if (candidate is BflStep.VALUES_AND_PERSPECTIVES && candidate == step && valueDeck is not null) + lines.Add(CardSort(form.CardSort ?? new BflCardSort(), valueDeck)); + if (lines.Count is 0) continue; @@ -437,6 +444,43 @@ public static class BflPromptComposer return text.Length is 0 ? "Die Person hat noch keine Angaben gemacht." : text.ToString().TrimEnd(); } + /// + /// Lists the card sort of the person for the chat of its step: every value of the deck with its definition and the + /// category the person chose, the values of their own, and those to look at more closely. + /// + /// + /// The values the person has not sorted come along, so that the chat can offer the card sort and take up a + /// sorting in the words of the person. The names of the values and of the categories are those the person sees. A + /// category or a choice whose value the deck does not know stays out. + /// + /// The card sort of the person; an empty one when they have not sorted anything. + /// The value deck in the language of the app. + /// The card sort as an item of the details of the step. + private static string CardSort(BflCardSort cardSort, BflValueDeck deck) + { + var text = new StringBuilder("- Werte-Sortierung nach ").Append(deck.Citation.Trim().TrimEnd('.')).Append(':'); + for (var index = 0; index < deck.Rounds.Count; index++) + { + text.Append(LINE_BREAK).Append($" - Runde {index + 1}:"); + foreach (var card in deck.Rounds[index]) + text.Append(LINE_BREAK).Append($" - {card.Name} ({card.Definition}): {cardSort.CategoryOf(card.Id).Title}"); + } + + text.Append(LINE_BREAK).Append(" - Eigene Werte der Person:"); + if (cardSort.OwnValues.Count is 0) + text.Append(" keine"); + + foreach (var value in cardSort.OwnValues) + { + var definition = value.Definition.Trim().ReplaceLineEndings(" "); + text.Append(LINE_BREAK).Append($" - {value.Name.Trim().ReplaceLineEndings(" ")} ({(definition.Length is 0 ? "ohne Erläuterung" : definition)})"); + } + + var focus = cardSort.FocusIn(deck); + text.Append(LINE_BREAK).Append(" - Genauer betrachten möchte die Person: ").Append(focus.Count is 0 ? "noch keine Auswahl" : string.Join(", ", focus.Select(valueId => cardSort.NameOf(valueId, deck)?.Trim()))); + return text.ToString(); + } + /// /// Lists the key details the forms still lack, step by step. /// diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareDialog.razor.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareDialog.razor.cs index cc4f167d2..022133f61 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareDialog.razor.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareDialog.razor.cs @@ -144,7 +144,7 @@ public partial class BflShareDialog : MSGComponentBase } var researchTopics = research.Where(entry => !string.IsNullOrWhiteSpace(entry.Value.Text)).Select(entry => entry.Key).ToList(); - this.source = new BflShareInput(forms, workStates, results, conversations, materials, researchTopics, false, APP_VERSION, DateTimeOffset.UtcNow, Culture); + this.source = new BflShareInput(forms, workStates, results, conversations, materials, researchTopics, false, APP_VERSION, DateTimeOffset.UtcNow, Culture, await this.LoadValueDeckAsync()); this.otherNames = string.Join('\n', BflShareNames.Suggest(forms, workStates, research.GetValueOrDefault(BflResearchTopic.PARTICIPANTS))); } catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or InvalidDataException) @@ -158,6 +158,23 @@ public partial class BflShareDialog : MSGComponentBase } } + /// + /// Reads the value deck in the language of the app, with which the session shows the card sort. + /// + /// The deck, or null when the app comes without a readable one; the session then leaves the card sort out. + private async Task LoadValueDeckAsync() + { + try + { + return await BflValueDeck.LoadAsync(Culture); + } + catch (Exception exception) when (exception is FileNotFoundException or FormatException) + { + this.Logger.LogError(exception, "Could not read the value deck of the BeyondFacts Lab to share the card sort."); + return null; + } + } + /// /// Puts the session together and anonymizes it with the names and terms the person listed; asks first when that /// would discard changes of the person in the preview. diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareInput.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareInput.cs index 19d620ac1..b8de2cd3a 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareInput.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareInput.cs @@ -15,4 +15,5 @@ namespace AIStudio.Assistants.BeyondFactsLab; /// The version of AI Studio, since the prompts of the lab change with it. /// When the person shares the session. /// The culture for dates and numbers, the one of the language of the app. -public sealed record BflShareInput(IReadOnlyDictionary Forms, IReadOnlyDictionary WorkStates, IReadOnlyDictionary Results, IReadOnlyDictionary> Conversations, IReadOnlyList Materials, IReadOnlyCollection ResearchTopics, bool IncludePersonalDisclosures, string AppVersion, DateTimeOffset SharedAtUtc, CultureInfo Culture); \ No newline at end of file +/// The value deck in the language of the app, with which the session shows the card sort of the person; null leaves the card sort out. +public sealed record BflShareInput(IReadOnlyDictionary Forms, IReadOnlyDictionary WorkStates, IReadOnlyDictionary Results, IReadOnlyDictionary> Conversations, IReadOnlyList Materials, IReadOnlyCollection ResearchTopics, bool IncludePersonalDisclosures, string AppVersion, DateTimeOffset SharedAtUtc, CultureInfo Culture, BflValueDeck? ValueDeck); \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareModelBuilder.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareModelBuilder.cs index 2bb823c71..b1ac0f503 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareModelBuilder.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflShareModelBuilder.cs @@ -8,8 +8,8 @@ namespace AIStudio.Assistants.BeyondFactsLab; /// /// /// -/// 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 +/// A session holds the details of every step, among them the card sort, 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 @@ -108,10 +108,37 @@ public static class BflShareModelBuilder AppendItem(text, field.Label, value); } + if (step is BflStep.VALUES_AND_PERSPECTIVES && input.ValueDeck is { } deck && form?.CardSort is { IsEmpty: false } cardSort) + { + any = true; + AppendCardSort(text, cardSort, deck); + } + if (!any) text.Append('_').Append(TB("No details.")).AppendLineLF("_"); } + /// + /// Appends the card sort of the person: the category of every value of the deck, the values of their own, and + /// those they chose to look at more closely, with the names the person saw. + /// + /// The session so far. + /// The card sort of the person. + /// The value deck in the language of the app. + private static void AppendCardSort(StringBuilder text, BflCardSort cardSort, BflValueDeck deck) + { + text.Append("**").Append(TB("Card sort")).AppendLineLF(":**").AppendLineLF(); + foreach (var card in deck.Cards) + AppendItem(text, card.Name, cardSort.CategoryOf(card.Id).Title); + + var ownValues = cardSort.OwnValues.Select(value => value.Definition.Trim().Length is 0 ? value.Name.Trim() : $"{value.Name.Trim()} ({value.Definition.Trim()})").ToList(); + AppendItem(text, TB("Own values"), ownValues.Count is 0 ? TB("None") : string.Join("; ", ownValues)); + + var focus = cardSort.FocusIn(deck).Select(valueId => cardSort.NameOf(valueId, deck)?.Trim()).ToList(); + AppendItem(text, TB("To look at more closely"), focus.Count is 0 ? TB("None") : string.Join(", ", focus)); + text.AppendLineLF(); + } + /// /// Appends the confirmed work state of a step, with all of its sections. /// diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStaleness.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStaleness.cs index d8eb29d3e..9314f70e7 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStaleness.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStaleness.cs @@ -94,7 +94,9 @@ public static class BflStaleness /// Computes the fingerprint of a form, which tells whether the person changed it. /// /// - /// Line endings and spaces around a value do not count, since they change nothing the model reads. + /// Line endings and spaces around a value do not count, since they change nothing the model reads. The card sort + /// counts as a detail of its step; without one, or with an empty one, the fingerprint is the same as before the lab + /// had a card sort, so that no confirmation of an older preparation turns into "Details changed". /// /// The form. /// The fingerprint, in hexadecimal digits. @@ -110,6 +112,33 @@ public static class BflStaleness text.Append(fieldId).Append(SEPARATOR).Append(normalized).Append(SEPARATOR); } + if (form.CardSort is { IsEmpty: false } cardSort) + AppendCardSort(text, cardSort); + return Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(text.ToString()))); } + + /// + /// Appends a card sort to the text a fingerprint is computed from. + /// + /// + /// Each part starts with a separator and its name, which no ID of a field begins with. The values to look at more + /// closely count as a set: the order the person chose them in is no ranking. + /// + /// The text so far. + /// The card sort. + private static void AppendCardSort(StringBuilder text, BflCardSort cardSort) + { + text.Append(SEPARATOR).Append("categories").Append(SEPARATOR); + foreach (var (valueId, category) in cardSort.Categories.Where(entry => entry.Value is not BflValueCategory.NONE).OrderBy(entry => entry.Key, StringComparer.Ordinal)) + text.Append(valueId).Append(SEPARATOR).Append(category.ToString()).Append(SEPARATOR); + + text.Append(SEPARATOR).Append("own values").Append(SEPARATOR); + foreach (var value in cardSort.OwnValues) + text.Append(value.Id).Append(SEPARATOR).Append(value.Name.Trim()).Append(SEPARATOR).Append(value.Definition.Trim()).Append(SEPARATOR); + + text.Append(SEPARATOR).Append("focus").Append(SEPARATOR); + foreach (var valueId in cardSort.Focus.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)) + text.Append(valueId).Append(SEPARATOR); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor index d569a1646..b102609b0 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor @@ -27,88 +27,106 @@ else if (this.HasPublicDetails) } +@* A section groups its fields under its heading, in a dashed frame; the fields without a section stand on their own. *@ - @foreach (var field in this.Fields) + @foreach (var section in this.Sections) { - switch (field.Kind) - { - case BflFieldKind.SINGLE_LINE: - case BflFieldKind.LINK: - - break; + + @if (section is not BflFormSection.NONE) + { + @section.Title + } - case BflFieldKind.MULTI_LINE: - - break; + @if (section.HasCardSort) + { + + } - case BflFieldKind.CHOICE: - - @foreach (var option in field.Options) + + @foreach (var field in this.FieldsOf(section)) + { + switch (field.Kind) { - @option.Label - } - - break; + case BflFieldKind.SINGLE_LINE: + case BflFieldKind.LINK: + + break; - case BflFieldKind.MULTI_CHOICE: -
- @LabelOf(field) - - @foreach (var option in field.Options) - { - - } - - @if (!string.IsNullOrWhiteSpace(field.Hint)) - { - @field.Hint + case BflFieldKind.MULTI_LINE: + + break; + + case BflFieldKind.CHOICE: + + @foreach (var option in field.Options) + { + @option.Label + } + + break; + + case BflFieldKind.MULTI_CHOICE: +
+ @LabelOf(field) + + @foreach (var option in field.Options) + { + + } + + @if (!string.IsNullOrWhiteSpace(field.Hint)) + { + @field.Hint + } +
+ break; } -
- break; - } + } +
+ } diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor.cs index 241839118..5279ce7a5 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepForm.razor.cs @@ -39,6 +39,30 @@ public partial class BflStepForm : MSGComponentBase private IReadOnlyList Fields => BflFieldCatalog.ForStep(this.Step); + /// + /// Gets the sections of the form, in the order of their first fields; a form without sections has NONE only. + /// + private IReadOnlyList Sections => [.. this.Fields.Select(definition => definition.Section).Distinct()]; + + /// + /// Gets the fields of a section of the form, in the order the catalog lists them. + /// + /// The section. + private IEnumerable FieldsOf(BflFormSection section) => this.Fields.Where(field => field.Section == section); + + /// + /// Gets the shadow of a section: the one of the dashed frame for a section, none for the fields without one. + /// + /// The section. + private static int SectionElevation(BflFormSection section) => section is BflFormSection.NONE ? 0 : 1; + + /// + /// Gets the CSS classes of a section: the dashed frame which groups related fields, none for the fields without a + /// section. + /// + /// The section. + private static string SectionClass(BflFormSection section) => section is BflFormSection.NONE ? string.Empty : "pa-3 border-dashed border rounded-lg"; + private IReadOnlyList MissingFields => BflMissingDetails.Of(this.Step, this.Form); private bool HasKeyDetails => this.Fields.Any(definition => definition.IsKeyDetail); diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepFormData.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepFormData.cs index 38b86ec0c..72c7be123 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepFormData.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflStepFormData.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace AIStudio.Assistants.BeyondFactsLab; /// @@ -5,14 +7,14 @@ namespace AIStudio.Assistants.BeyondFactsLab; /// /// /// The values are stored under the IDs of the fields of BflFieldCatalog. A value of a field this version does not -/// know, e.g., one a newer version added, stays as it is and is written back unchanged. +/// know, e.g., one a newer version added, stays as it is and is written back unchanged. Version 2 added the card sort. /// public sealed class BflStepFormData { /// /// The version of the stored form this version of AI Studio writes. Raise it with every new property. /// - public const int CURRENT_SCHEMA_VERSION = 1; + public const int CURRENT_SCHEMA_VERSION = 2; /// /// The character which separates the options of a field with several choices. @@ -29,6 +31,15 @@ public sealed class BflStepFormData /// public Dictionary Values { get; set; } = new(StringComparer.Ordinal); + /// + /// Gets or sets the card sort of the step "Values and perspectives", see BflCardSort. + /// + /// + /// Null for every other step, and until the person sorts a value there; the file then leaves it out. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public BflCardSort? CardSort { get; set; } + /// /// Gets the value of a field, or an empty string when the field has none. /// diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCard.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCard.cs new file mode 100644 index 000000000..92cb111e7 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCard.cs @@ -0,0 +1,25 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// A value of the value deck of the BeyondFacts Lab, which the person sorts in the step "Values and perspectives". +/// +/// +/// The card sort stores the ID only, so that the same sort shows in every language of the app. The ID never changes. +/// +public sealed class BflValueCard +{ + /// + /// Gets or sets the ID of the value, e.g., "honesty". + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the name of the value, e.g., "Honesty". + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets what the value means, in a few words, e.g., "Present statements sincerely". + /// + public string Definition { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor new file mode 100644 index 000000000..23439a3d7 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor @@ -0,0 +1,133 @@ +@inherits MSGComponentBase + +
+ @if (this.isDeckMissing) + { + + @T("AI Studio cannot show the card sort, because its values are missing or damaged.") + + } + else if (this.deck is { } shownDeck) + { + + @TA("Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality.") + + @string.Format(Culture, T("Based on: {0}"), shownDeck.Citation) + + + @for (var index = 0; index < shownDeck.Rounds.Count; index++) + { + var round = shownDeck.Rounds[index]; + var number = index + 1; + + + @foreach (var card in round) + { +
+ @card.Name + @card.Definition + + @foreach (var category in BflValueCategory.All) + { + + } + +
+ } +
+
+ } + + + + @TA("Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely.") + + + @foreach (var value in this.CardSort.OwnValues) + { + + + + + + } + + @if (this.CardSort.CanAddOwnValue) + { + + + + + @T("Add") + + + } + else + { + @string.Format(Culture, TA("You have added {0} values of your own, as many as the card sort takes."), BflCardSort.MAX_OWN_VALUES) + } + + + + + + @TA("Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking.") + + + @foreach (var card in shownDeck.Cards) + { + + } + @foreach (var value in this.CardSort.OwnValues) + { + + } + + @string.Format(Culture, T("{0} of at most {1} values chosen."), this.CardSort.FocusIn(shownDeck).Count, BflCardSort.MAX_FOCUS) + +
+ } +
\ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor.cs new file mode 100644 index 000000000..8ade1ec03 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCardSort.razor.cs @@ -0,0 +1,171 @@ +using System.Globalization; + +using AIStudio.Components; + +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// The card sort of the step "Values and perspectives" of the BeyondFacts Lab: a tab for each round of the deck, in +/// which the person sorts each value into a category, a tab for values of their own, and a tab for the two or three +/// values to look at more closely. +/// +/// +/// The card sort belongs to the details of its step: the component changes the form it gets and tells its parent, +/// which stores it. Every part is voluntary. The deck comes in the language of the app and is read again when that +/// language changes. +/// +public partial class BflValueCardSort : MSGComponentBase +{ + /// + /// What the component reads while the person has not sorted anything yet. It is never changed: the first change + /// gives the form a card sort of its own. + /// + private static readonly BflCardSort UNSORTED = new(); + + [Inject] + private ILogger Logger { get; init; } = null!; + + /// + /// Gets or sets the form of the step, whose card sort the component changes. + /// + [Parameter, EditorRequired] + public BflStepFormData Form { get; set; } = new(); + + /// + /// Gets or sets whether the card sort cannot be changed. + /// + [Parameter] + public bool Disabled { get; set; } + + /// + /// Gets or sets the callback raised after the card sort changed. + /// + [Parameter] + public EventCallback FormChanged { get; set; } + + /// + /// Gets or sets the CSS classes of the component. + /// + [Parameter] + public string Class { get; set; } = string.Empty; + + /// + /// The deck in the language of the app, or null until it is read or when the app comes without it. + /// + private BflValueDeck? deck; + + /// + /// The culture of the language whose deck the component read last. + /// + private CultureInfo? deckCulture; + + /// + /// Whether the app comes without a readable deck. + /// + private bool isDeckMissing; + + /// + /// The name of the value the person is about to add. + /// + private string newName = string.Empty; + + /// + /// What the value the person is about to add means to them. + /// + private string newDefinition = string.Empty; + + private static CultureInfo Culture => Tools.PluginSystem.I18N.I.Culture; + + /// + /// Gets the card sort to show. + /// + private BflCardSort CardSort => this.Form.CardSort ?? UNSORTED; + + /// + /// Gets the card sort to change, which the form gets with the first change. + /// + private BflCardSort ChangeableCardSort => this.Form.CardSort ??= new(); + + /// + /// Gets whether the person chose as many values to look at more closely as they may. + /// + private bool IsFocusFull => this.CardSort.Focus.Count >= BflCardSort.MAX_FOCUS; + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + if (Equals(this.deckCulture, Culture)) + return; + + this.deckCulture = Culture; + try + { + this.deck = await BflValueDeck.LoadAsync(Culture); + this.isDeckMissing = false; + } + catch (Exception exception) when (exception is FileNotFoundException or FormatException) + { + this.Logger.LogError(exception, "Could not read the value deck of the BeyondFacts Lab."); + this.deck = null; + this.isDeckMissing = true; + } + } + + /// + /// Gets the icon of the tab of a round: a check once the person sorted every value of it. + /// + /// The values of the round. + private string? RoundIcon(IReadOnlyList round) => round.All(card => this.CardSort.CategoryOf(card.Id) is not BflValueCategory.NONE) ? AppIcons.CARD_SORT_ROUND_DONE : null; + + private async Task SortAsync(string valueId, BflValueCategory category) + { + if (this.CardSort.CategoryOf(valueId) == category) + return; + + this.ChangeableCardSort.Sort(valueId, category); + await this.FormChanged.InvokeAsync(); + } + + private async Task AddOwnValueAsync() + { + if (this.ChangeableCardSort.AddOwnValue(this.newName, this.newDefinition) is null) + return; + + this.newName = string.Empty; + this.newDefinition = string.Empty; + await this.FormChanged.InvokeAsync(); + } + + private async Task ChangeOwnValueAsync(BflOwnValue value, string? name, string? definition) + { + if (this.ChangeableCardSort.ChangeOwnValue(value.Id, name ?? string.Empty, definition ?? string.Empty)) + await this.FormChanged.InvokeAsync(); + } + + private async Task RemoveOwnValueAsync(BflOwnValue value) + { + this.ChangeableCardSort.RemoveOwnValue(value.Id); + await this.FormChanged.InvokeAsync(); + } + + private async Task ChooseFocusAsync(IReadOnlyCollection? valueIds) + { + this.ChangeableCardSort.ChooseFocus(valueIds ?? []); + await this.FormChanged.InvokeAsync(); + } + + /// + /// Tells whether a value cannot be chosen to look at more closely, because the person chose as many as they may. + /// + /// The ID of the value. + private bool IsFocusBlocked(string valueId) => this.IsFocusFull && !this.CardSort.Focus.Contains(valueId, StringComparer.Ordinal); + + /// + /// Gets the check of the name of an own value, which refuses an empty name: the value keeps its former one then. + /// + private Func NameValidation => this.ValidateName; + + private string? ValidateName(string name) => string.IsNullOrWhiteSpace(name) ? TA("Please enter a name, or remove the value.") : null; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategory.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategory.cs new file mode 100644 index 000000000..533216aa5 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategory.cs @@ -0,0 +1,36 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// How important a value of the card sort of the BeyondFacts Lab is to the person, for the occasion they prepare. +/// +/// +/// The names are stored, so they never change. A value without a category stays open: it does not count as less +/// important. +/// +public enum BflValueCategory +{ + /// + /// The person has not sorted the value. It is never stored. + /// + NONE, + + /// + /// The value matters especially to the person. + /// + ESPECIALLY_IMPORTANT, + + /// + /// The value matters to the person as well. + /// + ALSO_IMPORTANT, + + /// + /// The value matters less for this occasion. + /// + LESS_IMPORTANT, + + /// + /// The person is unsure about the value. + /// + UNCLEAR, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategoryExtensions.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategoryExtensions.cs new file mode 100644 index 000000000..cdef56c43 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueCategoryExtensions.cs @@ -0,0 +1,28 @@ +namespace AIStudio.Assistants.BeyondFactsLab; + +public static class BflValueCategoryExtensions +{ + private static string TB(string fallbackEN) => Tools.PluginSystem.I18N.I.T(fallbackEN, typeof(BflValueCategoryExtensions).Namespace, nameof(BflValueCategoryExtensions)); + + /// The category. + extension(BflValueCategory category) + { + /// + /// Gets the name of the category, as the buttons of the card sort show it. + /// + public string Title => category switch + { + BflValueCategory.ESPECIALLY_IMPORTANT => TB("Especially important"), + BflValueCategory.ALSO_IMPORTANT => TB("Also important"), + BflValueCategory.LESS_IMPORTANT => TB("Less important for this occasion"), + BflValueCategory.UNCLEAR => TB("Unclear"), + + _ => TB("Not sorted yet"), + }; + + /// + /// Gets every category a person can choose, in the order the buttons show them. + /// + public static IReadOnlyList All => [BflValueCategory.ESPECIALLY_IMPORTANT, BflValueCategory.ALSO_IMPORTANT, BflValueCategory.LESS_IMPORTANT, BflValueCategory.UNCLEAR]; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueDeck.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueDeck.cs new file mode 100644 index 000000000..7f27865f0 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflValueDeck.cs @@ -0,0 +1,154 @@ +using System.Collections.Frozen; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AIStudio.Assistants.BeyondFactsLab; + +/// +/// The values which the person sorts in the step "Values and perspectives" of the BeyondFacts Lab, in rounds of a few +/// values each. +/// +/// +/// Lara Wüster (TUM) chose the values after the Personal Values Card Sort by Miller et al. (2001). The deck comes with +/// the app as one JSON file per language, below Assistants/BeyondFactsLab/Values; a development build reads it from +/// the disk, see AppResources. Every language has the same values in the same rounds, under the same IDs. +/// +public sealed class BflValueDeck +{ + /// + /// How many rounds the deck has. + /// + public const int ROUNDS = 3; + + /// + /// How many values each round has. + /// + public const int VALUES_PER_ROUND = 4; + + /// + /// The folder of the decks, relative to the project folder. + /// + private const string FOLDER = "Assistants/BeyondFactsLab/Values"; + + /// + /// The language whose deck the app shows when it has none in the language of the app. + /// + private const string FALLBACK_LANGUAGE = "en"; + + /// + /// The languages the app comes with a deck for, by their two-letter ISO codes. + /// + public static readonly FrozenSet LANGUAGES = FrozenSet.Create(StringComparer.Ordinal, "de", FALLBACK_LANGUAGE); + + /// + /// Gets or sets the source the deck is based on, as a citation. + /// + public string Citation { get; set; } = string.Empty; + + /// + /// Gets or sets the rounds, each with its values, in the order the person sorts them. + /// + public List> Rounds { get; set; } = []; + + /// + /// Gets every value of the deck, round by round. + /// + [JsonIgnore] + public IReadOnlyList Cards => [.. this.Rounds.SelectMany(round => round)]; + + /// + /// Finds a value of the deck. + /// + /// The ID of the value. + /// The value, or null when the deck has none with this ID. + public BflValueCard? Find(string valueId) => this.Rounds.SelectMany(round => round).FirstOrDefault(card => string.Equals(card.Id, valueId, StringComparison.Ordinal)); + + /// + /// Lists what is wrong with the deck: the number of rounds and values, IDs which are empty or appear twice, and + /// values without a name or a definition. + /// + /// The problems; empty when the deck is fine. + public IReadOnlyList Problems() + { + var problems = new List(); + if (string.IsNullOrWhiteSpace(this.Citation)) + problems.Add("The deck names no source."); + + if (this.Rounds.Count != ROUNDS) + problems.Add($"The deck has {this.Rounds.Count} rounds instead of {ROUNDS}."); + + for (var index = 0; index < this.Rounds.Count; index++) + { + var round = this.Rounds[index]; + if (round.Count != VALUES_PER_ROUND) + problems.Add($"Round {index + 1} has {round.Count} values instead of {VALUES_PER_ROUND}."); + } + + var ids = new HashSet(StringComparer.Ordinal); + foreach (var card in this.Rounds.SelectMany(round => round)) + { + if (string.IsNullOrWhiteSpace(card.Id)) + problems.Add("A value has no ID."); + else if (!ids.Add(card.Id)) + problems.Add($"The ID '{card.Id}' appears twice."); + + if (string.IsNullOrWhiteSpace(card.Name) || string.IsNullOrWhiteSpace(card.Definition)) + problems.Add($"The value '{card.Id}' lacks its name or its definition."); + } + + return problems; + } + + /// + /// Gets the file of the deck which the app shows in a language: the one of that language, or else the English one. + /// + /// The culture of the language, e.g., the culture of the app. + /// The file, relative to the project folder. + public static string PathOf(CultureInfo culture) + { + var language = LANGUAGES.Contains(culture.TwoLetterISOLanguageName) ? culture.TwoLetterISOLanguageName : FALLBACK_LANGUAGE; + return $"{FOLDER}/values.{language}.json"; + } + + /// + /// Reads the deck which the app shows in a language. + /// + /// The culture of the language, e.g., the culture of the app. + /// The cancellation token. + /// The deck. + /// The app comes without the deck. + /// The deck cannot be read, or something is wrong with it. Like a prompt with a placeholder without a value, it is a mistake of the app, which the lab reports the same way. + public static async Task LoadAsync(CultureInfo culture, CancellationToken token = default) + { + var path = PathOf(culture); + var text = await AppResources.ReadTextAsync(path, token); + if (string.IsNullOrWhiteSpace(text)) + throw new FileNotFoundException("The BeyondFacts Lab misses its value deck.", path); + + BflValueDeck? deck; + try + { + deck = JsonSerializer.Deserialize(text, BeyondFactsLabJson.PERSISTENCE); + } + catch (JsonException exception) + { + throw new FormatException($"The value deck '{path}' of the BeyondFacts Lab cannot be read.", exception); + } + + if (deck is null || IsNull(deck.Citation) || IsNull(deck.Rounds) || deck.Rounds.Any(round => IsNull(round) || round.Any(card => IsNull(card) || IsNull(card.Id) || IsNull(card.Name) || IsNull(card.Definition)))) + throw new FormatException($"The value deck '{path}' of the BeyondFacts Lab cannot be read."); + + var problems = deck.Problems(); + if (problems.Count > 0) + throw new FormatException($"The value deck '{path}' of the BeyondFacts Lab is wrong: {string.Join(" ", problems)}"); + + return deck; + } + + /// + /// Checks for null a value which the type system says cannot be null, but which JSON can make null. + /// + /// The value. + private static bool IsNull(T? value) => value is null; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflWorkStateSections.cs b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflWorkStateSections.cs index 3911e772b..4f7a49f7f 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflWorkStateSections.cs +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/BflWorkStateSections.cs @@ -40,7 +40,7 @@ public static class BflWorkStateSections new("CONSEQUENCES", BflStep.GOALS_AND_ROLE, "Folgen von Ziel und Rolle für ihren Auftritt."), new(OPEN_POINTS, BflStep.GOALS_AND_ROLE, "Offene Punkte."), - new("VALUES", BflStep.VALUES_AND_PERSPECTIVES, "Für den Anlass wichtige Werte und ihre Bedeutung."), + new("VALUES", BflStep.VALUES_AND_PERSPECTIVES, "Für den Anlass wichtige Werte und ihre Bedeutung, auch aus der Werte-Sortierung und den eigenen Werten der Person; die Werte, die sie genauer betrachtet hat, zuerst."), new("VALUE_CONFLICTS", BflStep.VALUES_AND_PERSPECTIVES, "Erkannte Wertkonflikte und die Abwägungen der Person."), new("PERSONAL_INVOLVEMENT", BflStep.VALUES_AND_PERSPECTIVES, "Erkenntnisse zur persönlichen Betroffenheit, nur soweit die Person sie ausdrücklich übernehmen möchte; sonst leer.") { IsPersonalDisclosure = true }, new("PERSPECTIVES", BflStep.VALUES_AND_PERSPECTIVES, "Berücksichtigte Perspektiven, jeweils mit dem Hinweis, ob sie auf Aussagen Betroffener oder auf Vermutungen beruhen."), diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/onboarding.md b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/onboarding.md index 77a6610c5..552f44f5e 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/onboarding.md +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/onboarding.md @@ -33,7 +33,7 @@ Für schnelle Texte wie eine Pressemitteilung passen der Chat von AI Studio oder 1. „Rahmendaten“: worum es bei der Veranstaltung geht, für wen, und was der Auftrag der Person ist. 2. „Wissen und Unsicherheit“: was sie sicher weiß, was unsicher ist und wo ihre Expertise endet. 3. „Ziele und Rolle“: was sie erreichen will und in welcher Rolle sie spricht. - 4. „Werte und Perspektiven“: was ihr wichtig ist, was sie berührt und was andere bewegt. + 4. „Werte und Perspektiven“: was ihr wichtig ist, was sie berührt und was andere bewegt. Dazu gehört eine freiwillige Werte-Sortierung: zwölf Werte in drei Runden, eigene Werte und die Auswahl von zwei oder drei Werten, die das Gespräch genauer betrachtet. Sie ist kein validierter Werte- oder Persönlichkeitstest. 5. „Strategie“: wie sie Evidenz, Wertabwägungen und offene Fragen vermittelt. 6. „Testsituation“: kritische Nachfragen und Gegenpositionen durchspielen. 7. „Ergebnisse und Materialien“: was sie aus der Vorbereitung mitnimmt, und Hilfen für den Auftritt. diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/steps/04-values.md b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/steps/04-values.md index bf034e5fa..635451faf 100644 --- a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/steps/04-values.md +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Prompts/steps/04-values.md @@ -16,18 +16,18 @@ Beginne sinngemäß so: „Hier können Sie erkunden, was Ihnen bei Ihrem Beitra ### Abschnitt „Was mir wichtig ist“ -Biete eine kurze Werte-Sortierung in drei Runden mit je vier Werten an. Sie beruht auf: Miller, W. R., C’de Baca, J., Matthews, D. B., & Wilbourne, P. L. (2001). Personal Values Card Sort. University of New Mexico. Nenne diese Quelle, wenn du die Sortierung anbietest. +Hier steht eine kurze Werte-Sortierung zur Verfügung: zwölf Werte in drei Runden mit je vier Werten. Sie beruht auf der Quelle, die in den Angaben dieses Schritts bei der Werte-Sortierung steht. Nenne diese Quelle, wenn du die Sortierung anbietest. -Die Person ordnet jeden Wert einer Kategorie zu: „Besonders wichtig“, „Auch wichtig“, „Weniger wichtig für diesen Anlass“ oder „Unklar“. Sie antwortet in eigenen Worten; ist eine Zuordnung uneindeutig, frage gezielt nach. Werte ohne Zuordnung bleiben offen und gelten nicht automatisch als weniger wichtig. Alle Zuordnungen bleiben änderbar. +Die Person sortiert über Schaltflächen in ihren Angaben, im Abschnitt „Was mir wichtig ist“ über diesem Gespräch. Dort ordnet sie jeden Wert einer Kategorie zu: „Besonders wichtig“, „Auch wichtig“, „Weniger wichtig für diesen Anlass“ oder „Unklar“. Sie kann dort auch eigene Werte mit eigener Bedeutung ergänzen und zwei oder drei Werte zum genaueren Betrachten auswählen. Den aktuellen Stand findest du in den Angaben dieses Schritts unter „Werte-Sortierung“, mit den Namen und Kategorien, die die Person sieht; er gilt für deine nächste Antwort. -- Runde 1: Ehrlichkeit (Aussagen aufrichtig darstellen), Selbstbestimmung (eigenständig urteilen und entscheiden), Sicherheit (Schäden vermeiden), Erkenntnis (verstehen und dazulernen). -- Runde 2: Gerechtigkeit (Vorteile und Belastungen fair verteilen), Fürsorge (Bedürfnisse anderer berücksichtigen), Erfolg (etwas erreichen und Wirkung entfalten), Loyalität (zu Menschen oder einer Organisation stehen). -- Runde 3: Offenheit (andere Sichtweisen ernsthaft prüfen), Verantwortung (für Folgen des eigenen Handelns einstehen), Respekt (andere als gleichwertige Gesprächspartner*innen behandeln), Fortschritt (Neues entwickeln und Verbesserungen ermöglichen). +- Hat die Person noch nicht sortiert, biete die Sortierung an und weise auf die Schaltflächen hin. Sie kann Werte auch hier im Gespräch in eigenen Worten zuordnen; ist eine Zuordnung uneindeutig, frage gezielt nach. +- Du kannst die Schaltflächen nicht ändern. Ordnet die Person im Gespräch anders zu als in der Sortierung, gilt das, was sie zuletzt gesagt hat; weise sie darauf hin, dass sie die Sortierung oben anpassen kann. +- Werte ohne Zuordnung bleiben offen und gelten nicht automatisch als weniger wichtig. Alle Zuordnungen bleiben änderbar. Danach, jeweils als eigene Nachricht: -1. „Welcher wichtige Wert fehlt Ihnen?“ Die Person kann eigene Werte mit eigener Bedeutung ergänzen. -2. „Welche zwei oder drei Werte möchten Sie für diesen Anlass genauer betrachten?“ Erzwinge keine Rangfolge. +1. „Welcher wichtige Wert fehlt Ihnen?“ Die Person kann eigene Werte mit eigener Bedeutung ergänzen, in der Sortierung oder im Gespräch. +2. „Welche zwei oder drei Werte möchten Sie für diesen Anlass genauer betrachten?“ Hat die Person in der Sortierung schon Werte dafür ausgewählt, greife sie auf, statt erneut zu fragen. Erzwinge keine Rangfolge. 3. Für jeden gewählten Wert: „Was bedeutet [Wert] für Ihren konkreten Beitrag? Woran würde man erkennen, dass Sie danach handeln?“ 4. „Gibt es eine Situation, in der zwei Ihrer Werte unterschiedliche Handlungen nahelegen?“ Braucht die Person Unterstützung, schlage einen möglichen Konflikt aus ihren bisherigen Angaben vor, etwa: „Könnten hier Loyalität zur Organisation und Offenheit über Grenzen der Forschung in Spannung stehen, oder passt das nicht?“ 5. Bestätigt sie einen Konflikt: „Wie möchten Sie damit umgehen, und was nehmen Sie dabei in Kauf?“ diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.de.json b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.de.json new file mode 100644 index 000000000..f649c82c3 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.de.json @@ -0,0 +1,23 @@ +{ + "citation": "Miller, W. R., C’de Baca, J., Matthews, D. B., & Wilbourne, P. L. (2001). Personal Values Card Sort. University of New Mexico.", + "rounds": [ + [ + { "id": "honesty", "name": "Ehrlichkeit", "definition": "Aussagen aufrichtig darstellen" }, + { "id": "self_determination", "name": "Selbstbestimmung", "definition": "Eigenständig urteilen und entscheiden" }, + { "id": "safety", "name": "Sicherheit", "definition": "Schäden vermeiden" }, + { "id": "knowledge", "name": "Erkenntnis", "definition": "Verstehen und dazulernen" } + ], + [ + { "id": "justice", "name": "Gerechtigkeit", "definition": "Vorteile und Belastungen fair verteilen" }, + { "id": "care", "name": "Fürsorge", "definition": "Bedürfnisse anderer berücksichtigen" }, + { "id": "success", "name": "Erfolg", "definition": "Etwas erreichen und Wirkung entfalten" }, + { "id": "loyalty", "name": "Loyalität", "definition": "Zu Menschen oder einer Organisation stehen" } + ], + [ + { "id": "openness", "name": "Offenheit", "definition": "Andere Sichtweisen ernsthaft prüfen" }, + { "id": "responsibility", "name": "Verantwortung", "definition": "Für Folgen des eigenen Handelns einstehen" }, + { "id": "respect", "name": "Respekt", "definition": "Andere als gleichwertige Gesprächspartner*innen behandeln" }, + { "id": "progress", "name": "Fortschritt", "definition": "Neues entwickeln und Verbesserungen ermöglichen" } + ] + ] +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.en.json b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.en.json new file mode 100644 index 000000000..ffce8264b --- /dev/null +++ b/app/MindWork AI Studio/Assistants/BeyondFactsLab/Values/values.en.json @@ -0,0 +1,23 @@ +{ + "citation": "Miller, W. R., C’de Baca, J., Matthews, D. B., & Wilbourne, P. L. (2001). Personal Values Card Sort. University of New Mexico.", + "rounds": [ + [ + { "id": "honesty", "name": "Honesty", "definition": "Present statements sincerely" }, + { "id": "self_determination", "name": "Self-determination", "definition": "Judge and decide independently" }, + { "id": "safety", "name": "Safety", "definition": "Avoid harm" }, + { "id": "knowledge", "name": "Knowledge", "definition": "Understand and keep learning" } + ], + [ + { "id": "justice", "name": "Justice", "definition": "Distribute benefits and burdens fairly" }, + { "id": "care", "name": "Care", "definition": "Consider the needs of others" }, + { "id": "success", "name": "Success", "definition": "Achieve something and make an impact" }, + { "id": "loyalty", "name": "Loyalty", "definition": "Stand by people or an organization" } + ], + [ + { "id": "openness", "name": "Openness", "definition": "Seriously examine other points of view" }, + { "id": "responsibility", "name": "Responsibility", "definition": "Answer for the consequences of one's own actions" }, + { "id": "respect", "name": "Respect", "definition": "Treat others as equal partners in a conversation" }, + { "id": "progress", "name": "Progress", "definition": "Develop new things and make improvements possible" } + ] + ] +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 6891784c3..79482f832 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -1405,6 +1405,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333 -- Demanding UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Demanding" +-- What touches me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T1464216834"] = "What touches me" + +-- What moves others +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T4052141584"] = "What moves others" + +-- What matters to me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T840264968"] = "What matters to me" + -- If you state in the event details that the event is not public, the lab never researches it on the web. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1053099788"] = "If you state in the event details that the event is not public, the lab never researches it on the web." @@ -1444,6 +1453,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266 -- Plan about 30 to 60 minutes for a preparation. You can pause at any time: AI Studio saves your details and conversations, and you continue where you left off. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266933::INFORMAL"] = "Plan about 30 to 60 minutes for a preparation. You can pause at any time: AI Studio saves your details and conversations, and you continue where you left off." +-- Value card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T194927200"] = "Value card sort" + -- Work through the seven steps, in any order you like. Each step has the form 'Your details' and a conversation of its own. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2001857602"] = "Work through the seven steps, in any order you like. Each step has the form 'Your details' and a conversation of its own." @@ -1474,6 +1486,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2980837 -- What the lab does not do UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2988041082"] = "What the lab does not do" +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819"] = "In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality." + +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819::INFORMAL"] = "In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality." + -- Let the lab start the conversation of a step, or write first and say what support you want, e.g., with your role, your strategy, or a round of critical questions. You can always say that you do not know. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3499941356"] = "Let the lab start the conversation of a step, or write first and say what support you want, e.g., with your role, your strategy, or a round of critical questions. You can always say that you do not know." @@ -2281,6 +2299,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMAIL::T3622173728 -- No confirmed work state. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1085223273"] = "No confirmed work state." +-- Card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T116016553"] = "Card sort" + -- No conversation. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1296316689"] = "No conversation." @@ -2335,15 +2356,24 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T34 -- About this session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3460157970"] = "About this session" +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3651870515"] = "Own values" + -- BeyondFacts Lab: a shared session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3731741448"] = "BeyondFacts Lab: a shared session" +-- To look at more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3757621498"] = "To look at more closely" + -- Left out, as it may contain personal disclosures. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3986361996"] = "Left out, as it may contain personal disclosures." -- Version of AI Studio UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T728223168"] = "Version of AI Studio" +-- None +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T810547195"] = "None" + -- Test situation UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Test situation" @@ -2446,6 +2476,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288: -- Public detail: the research of the lab receives it UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3459633363"] = "Public detail: the research of the lab receives it" +-- {0} of at most {1} values chosen. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2016759665"] = "{0} of at most {1} values chosen." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524"] = "Please enter a name, or remove the value." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524::INFORMAL"] = "Please enter a name, or remove the value." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734"] = "You have added {0} values of your own, as many as the card sort takes." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734::INFORMAL"] = "You have added {0} values of your own, as many as the card sort takes." + +-- Look more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2616202800"] = "Look more closely" + +-- Add +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2646845972"] = "Add" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372"] = "What it means to you" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372::INFORMAL"] = "What it means to you" + +-- Value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3511155050"] = "Value" + +-- AI Studio cannot show the card sort, because its values are missing or damaged. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3585110184"] = "AI Studio cannot show the card sort, because its values are missing or damaged." + +-- Based on: {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3634031667"] = "Based on: {0}" + +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3651870515"] = "Own values" + +-- Round {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3695292997"] = "Round {0}" + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772"] = "Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality." + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772::INFORMAL"] = "Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849"] = "Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849::INFORMAL"] = "Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460"] = "Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460::INFORMAL"] = "Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely." + +-- New value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T840803648"] = "New value" + +-- Not sorted yet +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2675795693"] = "Not sorted yet" + +-- Unclear +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2886397187"] = "Unclear" + +-- Less important for this occasion +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2929600398"] = "Less important for this occasion" + +-- Also important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3500243228"] = "Also important" + +-- Especially important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3612865466"] = "Especially important" + -- The file '{0}' in the test folder of the BeyondFacts Lab contains the placeholder '{1}', which the lab does not fill in this file. Please correct the file and try again. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLWORKSTATEGENERATOR::T1150375027"] = "The file '{0}' in the test folder of the BeyondFacts Lab contains the placeholder '{1}', which the lab does not fill in this file. Please correct the file and try again." diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 0119232f3..fae59a0d1 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -68,7 +68,7 @@ - + diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index d7f9137c1..f3c660866 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -1407,6 +1407,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333 -- Demanding UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Anspruchsvoll" +-- What touches me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T1464216834"] = "Was mich berührt" + +-- What moves others +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T4052141584"] = "Was andere bewegt" + +-- What matters to me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T840264968"] = "Was mir wichtig ist" + -- If you state in the event details that the event is not public, the lab never researches it on the web. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1053099788"] = "Wenn Sie in den Rahmendaten angeben, dass die Veranstaltung nicht öffentlich ist, recherchiert das Lab nie im Web zu ihr." @@ -1446,6 +1455,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266 -- Plan about 30 to 60 minutes for a preparation. You can pause at any time: AI Studio saves your details and conversations, and you continue where you left off. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266933::INFORMAL"] = "Plane für eine Vorbereitung etwa 30 bis 60 Minuten ein. Du kannst jederzeit unterbrechen: AI Studio speichert deine Angaben und Gespräche, und du machst später dort weiter, wo du aufgehört hast." +-- Value card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T194927200"] = "Werte-Sortierung" + -- Work through the seven steps, in any order you like. Each step has the form 'Your details' and a conversation of its own. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2001857602"] = "Bearbeiten Sie die sieben Schritte in beliebiger Reihenfolge. Jeder Schritt hat das Formular „Ihre Angaben“ und ein eigenes Gespräch." @@ -1476,6 +1488,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2980837 -- What the lab does not do UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2988041082"] = "Was das Lab nicht tut" +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819"] = "Im Schritt „{0}“ können Sie zwölf Werte in drei Runden danach sortieren, wie wichtig sie Ihnen für diesen Anlass sind. Sie können eigene Werte ergänzen und zwei oder drei Werte auswählen, die Sie genauer betrachten möchten. Das Gespräch dieses Schritts kennt Ihre Werte-Sortierung und baut darauf auf; die späteren Schritte erfahren über seinen bestätigten Arbeitsstand, was Sie daraus mitnehmen. Die Werte-Sortierung ist freiwillig und kein validierter Werte- oder Persönlichkeitstest." + +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819::INFORMAL"] = "Im Schritt „{0}“ kannst du zwölf Werte in drei Runden danach sortieren, wie wichtig sie dir für diesen Anlass sind. Du kannst eigene Werte ergänzen und zwei oder drei Werte auswählen, die du genauer betrachten möchtest. Das Gespräch dieses Schritts kennt deine Werte-Sortierung und baut darauf auf; die späteren Schritte erfahren über seinen bestätigten Arbeitsstand, was du daraus mitnimmst. Die Werte-Sortierung ist freiwillig und kein validierter Werte- oder Persönlichkeitstest." + -- Let the lab start the conversation of a step, or write first and say what support you want, e.g., with your role, your strategy, or a round of critical questions. You can always say that you do not know. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3499941356"] = "Lassen Sie das Lab das Gespräch eines Schritts beginnen, oder schreiben Sie zuerst und sagen Sie, wobei Sie Unterstützung möchten, etwa bei Ihrer Rolle, Ihrer Strategie oder mit einer Runde kritischer Fragen. Sie können jederzeit „Ich weiß es nicht“ antworten." @@ -2283,6 +2301,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMAIL::T3622173728 -- No confirmed work state. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1085223273"] = "Kein bestätigter Arbeitsstand." +-- Card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T116016553"] = "Werte-Sortierung" + -- No conversation. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1296316689"] = "Kein Gespräch." @@ -2337,15 +2358,24 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T34 -- About this session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3460157970"] = "Über diese Sitzung" +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3651870515"] = "Eigene Werte" + -- BeyondFacts Lab: a shared session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3731741448"] = "BeyondFacts Lab: eine geteilte Sitzung" +-- To look at more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3757621498"] = "Genauer betrachten" + -- Left out, as it may contain personal disclosures. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3986361996"] = "Ausgelassen, da es persönliche Angaben enthalten kann." -- Version of AI Studio UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T728223168"] = "Version von AI Studio" +-- None +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T810547195"] = "Keine" + -- Test situation UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Testsituation" @@ -2448,6 +2478,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288: -- Public detail: the research of the lab receives it UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3459633363"] = "Öffentliche Angabe: Die Recherche des Labs erhält sie" +-- {0} of at most {1} values chosen. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2016759665"] = "{0} von höchstens {1} Werten ausgewählt." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524"] = "Bitte geben Sie einen Namen ein oder entfernen Sie den Wert." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524::INFORMAL"] = "Bitte gib einen Namen ein oder entferne den Wert." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734"] = "Sie haben {0} eigene Werte hinzugefügt, so viele, wie die Werte-Sortierung aufnimmt." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734::INFORMAL"] = "Du hast {0} eigene Werte hinzugefügt, so viele, wie die Werte-Sortierung aufnimmt." + +-- Look more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2616202800"] = "Genauer betrachten" + +-- Add +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2646845972"] = "Hinzufügen" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372"] = "Was er für Sie bedeutet" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372::INFORMAL"] = "Was er für dich bedeutet" + +-- Value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3511155050"] = "Wert" + +-- AI Studio cannot show the card sort, because its values are missing or damaged. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3585110184"] = "AI Studio kann die Werte-Sortierung nicht anzeigen, weil ihre Werte fehlen oder beschädigt sind." + +-- Based on: {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3634031667"] = "Basierend auf: {0}" + +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3651870515"] = "Eigene Werte" + +-- Round {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3695292997"] = "Runde {0}" + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772"] = "Ordnen Sie jeden Wert danach ein, wie wichtig er Ihnen für diesen Anlass ist. Ein Wert, den Sie nicht einordnen, bleibt offen und gilt nicht als weniger wichtig. Ergänzen Sie anschließend fehlende Werte und wählen Sie zwei oder drei Werte aus, die Sie im Gespräch genauer betrachten möchten. Alles ist freiwillig und bleibt änderbar; Sie können diesen Schritt auch ohne Werte-Sortierung abschließen. Die Werte-Sortierung ist kein validierter Werte- oder Persönlichkeitstest." + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772::INFORMAL"] = "Ordne jeden Wert danach ein, wie wichtig er dir für diesen Anlass ist. Ein Wert, den du nicht einordnest, bleibt offen und gilt nicht als weniger wichtig. Ergänze anschließend fehlende Werte und wähle zwei oder drei Werte aus, die du im Gespräch genauer betrachten möchtest. Alles ist freiwillig und bleibt änderbar; du kannst diesen Schritt auch ohne Werte-Sortierung abschließen. Die Werte-Sortierung ist kein validierter Werte- oder Persönlichkeitstest." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849"] = "Welche zwei oder drei Werte möchten Sie für diesen Anlass genauer betrachten? Das Gespräch fragt dann, was diese Werte für Ihren Beitrag bedeuten und wo sie unterschiedliche Handlungen nahelegen könnten. Ihre Auswahl ist keine Rangfolge." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849::INFORMAL"] = "Welche zwei oder drei Werte möchtest du für diesen Anlass genauer betrachten? Das Gespräch fragt dann, was diese Werte für deinen Beitrag bedeuten und wo sie unterschiedliche Handlungen nahelegen könnten. Deine Auswahl ist keine Rangfolge." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460"] = "Welcher wichtige Wert fehlt Ihnen? Fügen Sie ihn hinzu und geben Sie an, was er für Sie bedeutet. Sie können auch eigene Werte auswählen, die Sie genauer betrachten möchten." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460::INFORMAL"] = "Welcher wichtige Wert fehlt dir? Füge ihn hinzu und gib an, was er für dich bedeutet. Du kannst auch eigene Werte auswählen, die du genauer betrachten möchtest." + +-- New value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T840803648"] = "Neuer Wert" + +-- Not sorted yet +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2675795693"] = "Noch nicht zugeordnet" + +-- Unclear +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2886397187"] = "Unklar" + +-- Less important for this occasion +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2929600398"] = "Weniger wichtig für diesen Anlass" + +-- Also important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3500243228"] = "Auch wichtig" + +-- Especially important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3612865466"] = "Besonders wichtig" + -- The file '{0}' in the test folder of the BeyondFacts Lab contains the placeholder '{1}', which the lab does not fill in this file. Please correct the file and try again. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLWORKSTATEGENERATOR::T1150375027"] = "Die Datei „{0}“ im Testordner des BeyondFacts Labs enthält den Platzhalter „{1}“, den das Lab in dieser Datei nicht ausfüllt. Bitte korrigieren Sie die Datei und versuchen Sie es erneut." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index f3f2804fd..c57f75bf7 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -1407,6 +1407,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T974131333 -- Demanding UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFIELDTEXTS::T985014450"] = "Demanding" +-- What touches me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T1464216834"] = "What touches me" + +-- What moves others +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T4052141584"] = "What moves others" + +-- What matters to me +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLFORMSECTIONEXTENSIONS::T840264968"] = "What matters to me" + -- If you state in the event details that the event is not public, the lab never researches it on the web. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1053099788"] = "If you state in the event details that the event is not public, the lab never researches it on the web." @@ -1446,6 +1455,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266 -- Plan about 30 to 60 minutes for a preparation. You can pause at any time: AI Studio saves your details and conversations, and you continue where you left off. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T1637266933::INFORMAL"] = "Plan about 30 to 60 minutes for a preparation. You can pause at any time: AI Studio saves your details and conversations, and you continue where you left off." +-- Value card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T194927200"] = "Value card sort" + -- Work through the seven steps, in any order you like. Each step has the form 'Your details' and a conversation of its own. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2001857602"] = "Work through the seven steps, in any order you like. Each step has the form 'Your details' and a conversation of its own." @@ -1476,6 +1488,12 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2980837 -- What the lab does not do UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T2988041082"] = "What the lab does not do" +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819"] = "In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality." + +-- In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3258811819::INFORMAL"] = "In the step '{0}', you can sort twelve values in three rounds by how important they are to you for this occasion, add values of your own, and choose two or three to look at more closely. The conversation of the step knows your card sort and builds on it; the later steps learn what you take from it through its confirmed work state. The card sort is voluntary and no validated test of values or personality." + -- Let the lab start the conversation of a step, or write first and say what support you want, e.g., with your role, your strategy, or a round of critical questions. You can always say that you do not know. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLINTRODUCTION::T3499941356"] = "Let the lab start the conversation of a step, or write first and say what support you want, e.g., with your role, your strategy, or a round of critical questions. You can always say that you do not know." @@ -2283,6 +2301,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMAIL::T3622173728 -- No confirmed work state. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1085223273"] = "No confirmed work state." +-- Card sort +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T116016553"] = "Card sort" + -- No conversation. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T1296316689"] = "No conversation." @@ -2337,15 +2358,24 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T34 -- About this session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3460157970"] = "About this session" +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3651870515"] = "Own values" + -- BeyondFacts Lab: a shared session UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3731741448"] = "BeyondFacts Lab: a shared session" +-- To look at more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3757621498"] = "To look at more closely" + -- Left out, as it may contain personal disclosures. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T3986361996"] = "Left out, as it may contain personal disclosures." -- Version of AI Studio UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T728223168"] = "Version of AI Studio" +-- None +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSHAREMODELBUILDER::T810547195"] = "None" + -- Test situation UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPEXTENSIONS::T1049923473"] = "Test situation" @@ -2448,6 +2478,84 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3011046288: -- Public detail: the research of the lab receives it UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLSTEPFORM::T3459633363"] = "Public detail: the research of the lab receives it" +-- {0} of at most {1} values chosen. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2016759665"] = "{0} of at most {1} values chosen." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524"] = "Please enter a name, or remove the value." + +-- Please enter a name, or remove the value. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2083996524::INFORMAL"] = "Please enter a name, or remove the value." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734"] = "You have added {0} values of your own, as many as the card sort takes." + +-- You have added {0} values of your own, as many as the card sort takes. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2105692734::INFORMAL"] = "You have added {0} values of your own, as many as the card sort takes." + +-- Look more closely +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2616202800"] = "Look more closely" + +-- Add +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T2646845972"] = "Add" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372"] = "What it means to you" + +-- What it means to you +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T32862372::INFORMAL"] = "What it means to you" + +-- Value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3511155050"] = "Value" + +-- AI Studio cannot show the card sort, because its values are missing or damaged. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3585110184"] = "AI Studio cannot show the card sort, because its values are missing or damaged." + +-- Based on: {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3634031667"] = "Based on: {0}" + +-- Own values +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3651870515"] = "Own values" + +-- Round {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3695292997"] = "Round {0}" + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772"] = "Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality." + +-- Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T3913548772::INFORMAL"] = "Sort each value by how important it is to you for this occasion. A value you do not sort stays open; it does not count as less important. Then add values you miss, and choose two or three values to look at more closely in the conversation. Everything is voluntary and stays changeable, and you can finish this step without the card sort. It is no validated test of values or personality." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849"] = "Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking." + +-- Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T4047838849::INFORMAL"] = "Which two or three values would you like to look at more closely for this occasion? The conversation then asks what they mean for your contribution and where they might pull in different directions. Your choice is no ranking." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460"] = "Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely." + +-- Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T508946460::INFORMAL"] = "Which important value do you miss? Add it with what it means to you. You can also choose your own values to look at more closely." + +-- New value +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECARDSORT::T840803648"] = "New value" + +-- Not sorted yet +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2675795693"] = "Not sorted yet" + +-- Unclear +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2886397187"] = "Unclear" + +-- Less important for this occasion +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T2929600398"] = "Less important for this occasion" + +-- Also important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3500243228"] = "Also important" + +-- Especially important +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLVALUECATEGORYEXTENSIONS::T3612865466"] = "Especially important" + -- The file '{0}' in the test folder of the BeyondFacts Lab contains the placeholder '{1}', which the lab does not fill in this file. Please correct the file and try again. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BEYONDFACTSLAB::BFLWORKSTATEGENERATOR::T1150375027"] = "The file '{0}' in the test folder of the BeyondFacts Lab contains the placeholder '{1}', which the lab does not fill in this file. Please correct the file and try again." diff --git a/app/MindWork AI Studio/Tools/AppIcons.cs b/app/MindWork AI Studio/Tools/AppIcons.cs index 2f81369a9..c8b6ac7a6 100644 --- a/app/MindWork AI Studio/Tools/AppIcons.cs +++ b/app/MindWork AI Studio/Tools/AppIcons.cs @@ -212,6 +212,9 @@ public static class AppIcons /// Marks an operation that was canceled, such as an assistant run or a media import. public const string CANCELED = LucideIcons.CIRCLE_SLASH; + /// Marks a round of a card sort whose values the person sorted all, such as in the BeyondFacts Lab. + public const string CARD_SORT_ROUND_DONE = LucideIcons.CIRCLE_CHECK; + /// Marks the category of an item, such as an assistant. public const string CATEGORY = LucideIcons.SHAPES; @@ -806,6 +809,9 @@ public static class AppIcons /// Stands for what comes out of an assistant, such as the expected output or the output mode. public const string OUTPUT = LucideIcons.LOG_OUT; + /// Marks the values a person adds to a card sort because its deck misses them, such as in the BeyondFacts Lab. + public const string OWN_VALUES = LucideIcons.HEART_PLUS; + /// Marks Pandoc, the document converter that AI Studio uses. public const string PANDOC = LucideIcons.FILE_COG; @@ -1196,6 +1202,9 @@ public static class AppIcons /// Moves a result back into the input field so that you can work on it again. public const string USE_AS_INPUT = LucideIcons.TEXT_CURSOR_INPUT; + /// Marks the values a person chooses to look at more closely, such as after the card sort of the BeyondFacts Lab. + public const string VALUE_FOCUS = LucideIcons.FOCUS; + /// Marks a list value, such as in the metadata of an assistant plugin. public const string VALUE_LIST = LucideIcons.LIST; diff --git a/app/Tests/BeyondFactsLab/BeyondFactsLabStoreTests.cs b/app/Tests/BeyondFactsLab/BeyondFactsLabStoreTests.cs index 089bc5861..bf85160de 100644 --- a/app/Tests/BeyondFactsLab/BeyondFactsLabStoreTests.cs +++ b/app/Tests/BeyondFactsLab/BeyondFactsLabStoreTests.cs @@ -343,6 +343,68 @@ public sealed class BeyondFactsLabStoreTests } } + [Test] + public async Task ACardSortIsStoredWithTheFormOfItsStep() + { + var store = this.CreateStore(); + var created = await store.CreateAsync("Panel"); + var form = new BflStepFormData { CardSort = new() }; + form.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + var ownValue = form.CardSort.AddOwnValue("Neugier", "Fragen stellen")!; + form.CardSort.ChooseFocus(["honesty", ownValue.Id]); + + await store.SaveFormAsync(created.ProjectId, BflStep.VALUES_AND_PERSPECTIVES, form); + var loaded = (await this.CreateStore().LoadFormAsync(created.ProjectId, BflStep.VALUES_AND_PERSPECTIVES)).CardSort; + + using (Assert.EnterMultipleScope()) + { + Assert.That(loaded, Is.Not.Null); + Assert.That(loaded!.CategoryOf("honesty"), Is.EqualTo(BflValueCategory.ESPECIALLY_IMPORTANT)); + Assert.That(loaded.OwnValues.Select(value => (value.Id, value.Name, value.Definition)), Is.EqualTo(new[] { (ownValue.Id, "Neugier", "Fragen stellen") })); + Assert.That(loaded.Focus, Is.EqualTo(new[] { "honesty", ownValue.Id })); + } + } + + [Test] + public async Task AFormWithoutACardSortStoresNone() + { + var store = this.CreateStore(); + var created = await store.CreateAsync("Panel"); + var form = new BflStepFormData(); + form.Set(BflFieldCatalog.CORE_THESES, "Thesis"); + + await store.SaveFormAsync(created.ProjectId, BflStep.KNOWLEDGE, form); + var json = await File.ReadAllTextAsync(Path.Combine(this.RootDirectory, created.ProjectId.ToString("D"), "steps", "02-knowledge", "form.json")); + + Assert.That(json, Does.Not.Contain("cardSort")); + } + + [Test] + public async Task AFormOfTheFirstVersionIsStillOpened() + { + var store = this.CreateStore(); + var created = await store.CreateAsync("Panel"); + await this.WriteFormFileAsync(created.ProjectId, "04-values", """{ "schemaVersion": 1, "values": { "values_and_concerns": "Text" } }"""); + + var form = await store.LoadFormAsync(created.ProjectId, BflStep.VALUES_AND_PERSPECTIVES); + + using (Assert.EnterMultipleScope()) + { + Assert.That(form.Get(BflFieldCatalog.VALUES_AND_CONCERNS), Is.EqualTo("Text")); + Assert.That(form.CardSort, Is.Null); + } + } + + [Test] + public async Task AFormWithABrokenCardSortIsNotOpened() + { + var store = this.CreateStore(); + var created = await store.CreateAsync("Panel"); + await this.WriteFormFileAsync(created.ProjectId, "04-values", """{ "schemaVersion": 2, "values": {}, "cardSort": { "categories": {}, "ownValues": [ null ], "focus": [] } }"""); + + await Assert.ThrowsAsync(async () => await store.LoadFormAsync(created.ProjectId, BflStep.VALUES_AND_PERSPECTIVES)); + } + [Test] public async Task AStepWithoutAFormGetsAnEmptyOne() { @@ -401,7 +463,7 @@ public sealed class BeyondFactsLabStoreTests [Test] public async Task AFormOfANewerVersionIsNotOverwritten() { - const string NEWER = """{ "schemaVersion": 2, "values": {} }"""; + const string NEWER = """{ "schemaVersion": 3, "values": {} }"""; var store = this.CreateStore(); var created = await store.CreateAsync("Panel"); var formPath = await this.WriteFormFileAsync(created.ProjectId, "05-strategy", NEWER); diff --git a/app/Tests/BeyondFactsLab/BflCardSortPromptTests.cs b/app/Tests/BeyondFactsLab/BflCardSortPromptTests.cs new file mode 100644 index 000000000..dab47a133 --- /dev/null +++ b/app/Tests/BeyondFactsLab/BflCardSortPromptTests.cs @@ -0,0 +1,152 @@ +using System.Globalization; + +using AIStudio.Assistants.BeyondFactsLab; +using AIStudio.Settings.DataModel; + +namespace AIStudio.Tests.BeyondFactsLab; + +/// +/// Checks how the card sort of the BeyondFacts Lab reaches the chat of its step: as part of the details, with every +/// value of the deck, and never in the chats of the other steps. +/// +[TestFixture] +public sealed class BflCardSortPromptTests +{ + private const string CORE = "{{DETAILS}}"; + + private static readonly BflPromptParts PARTS = new(CORE, "Leitfaden", "Schritt", "Anrede", string.Empty); + + [Test] + public async Task TheCardSortReachesOnlyTheChatOfItsOwnStep() + { + var deck = await EnglishDeckAsync(); + var values = new BflStepFormData { CardSort = new() }; + values.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + values.Set(BflFieldCatalog.VALUES_AND_CONCERNS, "Offenheit"); + var forms = new Dictionary { [BflStep.VALUES_AND_PERSPECTIVES] = values }; + + foreach (var step in BflStep.All) + { + var prompt = BflPromptComposer.Compose(PARTS, step, forms, [], "Deutsch", valueDeck: deck); + using (Assert.EnterMultipleScope()) + { + Assert.That(prompt.Contains("Werte-Sortierung", StringComparison.Ordinal), Is.EqualTo(step is BflStep.VALUES_AND_PERSPECTIVES), step.ToString()); + Assert.That(prompt, Does.Contain("Offenheit"), "The other details of the step reach every chat, as before."); + } + } + } + + [Test] + public async Task TheChatSeesEveryValueWithItsCategoryAndTheSource() + { + var deck = await EnglishDeckAsync(); + var values = new BflStepFormData { CardSort = new() }; + values.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + values.CardSort.Sort("progress", BflValueCategory.LESS_IMPORTANT); + + var prompt = Compose(values, deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(prompt, Does.Contain("- Werte-Sortierung nach Miller, W. R., C’de Baca, J., Matthews, D. B., & Wilbourne, P. L. (2001). Personal Values Card Sort. University of New Mexico:")); + Assert.That(prompt, Does.Contain("\n - Runde 1:\n - Honesty (Present statements sincerely): Especially important\n")); + Assert.That(prompt, Does.Contain($" - Safety (Avoid harm): {BflValueCategory.NONE.Title}\n")); + Assert.That(prompt, Does.Contain($" - Progress (Develop new things and make improvements possible): {BflValueCategory.LESS_IMPORTANT.Title}\n")); + Assert.That(prompt, Does.Contain("\n - Runde 3:")); + foreach (var card in deck.Cards) + Assert.That(prompt, Does.Contain($" - {card.Name} ({card.Definition}): "), card.Id); + } + } + + [Test] + public async Task TheChatSeesTheOwnValuesAndTheValuesToLookAtMoreClosely() + { + var deck = await EnglishDeckAsync(); + var values = new BflStepFormData { CardSort = new() }; + var curiosity = values.CardSort.AddOwnValue("Curiosity", "Ask questions")!; + values.CardSort.AddOwnValue("Patience", string.Empty); + values.CardSort.ChooseFocus(["honesty", curiosity.Id]); + + var prompt = Compose(values, deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(prompt, Does.Contain(" - Eigene Werte der Person:\n - Curiosity (Ask questions)\n - Patience (ohne Erläuterung)\n")); + Assert.That(prompt, Does.Contain(" - Genauer betrachten möchte die Person: Honesty, Curiosity")); + } + } + + [Test] + public async Task WithoutAnySortingTheChatStillSeesTheDeckToOfferIt() + { + var deck = await EnglishDeckAsync(); + + var prompt = Compose(new BflStepFormData(), deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(prompt, Does.Contain($" - Honesty (Present statements sincerely): {BflValueCategory.NONE.Title}")); + Assert.That(prompt, Does.Contain(" - Eigene Werte der Person: keine")); + Assert.That(prompt, Does.Contain(" - Genauer betrachten möchte die Person: noch keine Auswahl")); + } + } + + [Test] + public async Task AValueTheDeckDoesNotKnowStaysOut() + { + var deck = await EnglishDeckAsync(); + var values = new BflStepFormData { CardSort = new() { Focus = ["value_of_another_deck"] } }; + values.CardSort.Sort("value_of_another_deck", BflValueCategory.ESPECIALLY_IMPORTANT); + + var prompt = Compose(values, deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(prompt, Does.Not.Contain("value_of_another_deck")); + Assert.That(prompt, Does.Contain("noch keine Auswahl")); + } + } + + [Test] + public void WithoutADeckThePromptLeavesTheCardSortOut() + { + var values = new BflStepFormData { CardSort = new() }; + values.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + + var prompt = BflPromptComposer.Compose(PARTS, BflStep.VALUES_AND_PERSPECTIVES, new Dictionary { [BflStep.VALUES_AND_PERSPECTIVES] = values }, [], "Deutsch"); + + Assert.That(prompt, Does.Not.Contain("Werte-Sortierung")); + } + + [Test] + public async Task ThePromptOfTheStepLeavesTheValuesToTheDeck() + { + var step = await BflPromptLibrary.ReadBuiltInAsync(BflPromptLibrary.StepPath(BflStep.VALUES_AND_PERSPECTIVES)); + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("de")); + + using (Assert.EnterMultipleScope()) + { + foreach (var card in deck.Cards) + Assert.That(step, Does.Not.Contain(card.Definition).IgnoreCase, $"The prompt of the step repeats the value '{card.Id}' of the deck."); + + Assert.That(step, Does.Contain("Werte-Sortierung")); + } + } + + [Test] + public async Task TheRealPromptOfTheStepCarriesTheCardSort() + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("de")); + var parts = await BflPromptLibrary.LoadPartsAsync(BflStep.VALUES_AND_PERSPECTIVES, AddressForm.FORMAL); + var values = new BflStepFormData { CardSort = new() }; + values.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + + var prompt = BflPromptComposer.Compose(parts, BflStep.VALUES_AND_PERSPECTIVES, new Dictionary { [BflStep.VALUES_AND_PERSPECTIVES] = values }, [], "Deutsch", valueDeck: deck); + + Assert.That(prompt, Does.Contain(" - Ehrlichkeit (Aussagen aufrichtig darstellen): ")); + } + + private static string Compose(BflStepFormData values, BflValueDeck deck) => BflPromptComposer.Compose(PARTS, BflStep.VALUES_AND_PERSPECTIVES, new Dictionary { [BflStep.VALUES_AND_PERSPECTIVES] = values }, [], "Deutsch", valueDeck: deck); + + private static Task EnglishDeckAsync() => BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); +} \ No newline at end of file diff --git a/app/Tests/BeyondFactsLab/BflCardSortTests.cs b/app/Tests/BeyondFactsLab/BflCardSortTests.cs new file mode 100644 index 000000000..034b833bb --- /dev/null +++ b/app/Tests/BeyondFactsLab/BflCardSortTests.cs @@ -0,0 +1,172 @@ +using System.Globalization; + +using AIStudio.Assistants.BeyondFactsLab; + +namespace AIStudio.Tests.BeyondFactsLab; + +/// +/// Checks the card sort of the BeyondFacts Lab: values stay open until the person sorts them, values of their own keep +/// their limits, and at most three values get looked at more closely. +/// +[TestFixture] +public sealed class BflCardSortTests +{ + [Test] + public void AValueStaysOpenUntilThePersonSortsIt() + { + var cardSort = new BflCardSort(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(cardSort.CategoryOf("honesty"), Is.EqualTo(BflValueCategory.NONE)); + Assert.That(cardSort.IsEmpty, Is.True); + } + } + + [Test] + public void ASortedValueCanBeOpenedAgain() + { + var cardSort = new BflCardSort(); + cardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + cardSort.Sort("honesty", BflValueCategory.UNCLEAR); + var changed = cardSort.CategoryOf("honesty"); + + cardSort.Sort("honesty", BflValueCategory.NONE); + + using (Assert.EnterMultipleScope()) + { + Assert.That(changed, Is.EqualTo(BflValueCategory.UNCLEAR)); + Assert.That(cardSort.Categories, Is.Empty); + Assert.That(cardSort.IsEmpty, Is.True); + } + } + + [Test] + public void TheButtonsOfferEveryCategoryButNone() + { + Assert.That(BflValueCategory.All, Is.EquivalentTo(Enum.GetValues().Where(category => category is not BflValueCategory.NONE))); + } + + [Test] + public void AnOwnValueNeedsAName() + { + var cardSort = new BflCardSort(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(cardSort.AddOwnValue(" ", "Meaning"), Is.Null); + Assert.That(cardSort.OwnValues, Is.Empty); + } + } + + [Test] + public void AnOwnValueIsTrimmedAndCut() + { + var cardSort = new BflCardSort(); + + var value = cardSort.AddOwnValue($" {new string('n', BflCardSort.MAX_NAME_LENGTH + 5)} ", new string('d', BflCardSort.MAX_DEFINITION_LENGTH + 5)); + + using (Assert.EnterMultipleScope()) + { + Assert.That(value!.Name, Has.Length.EqualTo(BflCardSort.MAX_NAME_LENGTH)); + Assert.That(value.Definition, Has.Length.EqualTo(BflCardSort.MAX_DEFINITION_LENGTH)); + Assert.That(value.Id, Is.Not.Empty); + Assert.That(cardSort.IsEmpty, Is.False); + } + } + + [Test] + public void APersonAddsAtMostTenValuesOfTheirOwn() + { + var cardSort = new BflCardSort(); + for (var index = 0; index < BflCardSort.MAX_OWN_VALUES; index++) + cardSort.AddOwnValue($"Value {index}", string.Empty); + + using (Assert.EnterMultipleScope()) + { + Assert.That(cardSort.CanAddOwnValue, Is.False); + Assert.That(cardSort.AddOwnValue("One more", string.Empty), Is.Null); + Assert.That(cardSort.OwnValues, Has.Count.EqualTo(BflCardSort.MAX_OWN_VALUES)); + } + } + + [Test] + public void AnOwnValueKeepsItsNameWhenTheNewOneIsEmpty() + { + var cardSort = new BflCardSort(); + var value = cardSort.AddOwnValue("Curiosity", "Ask questions")!; + + var emptied = cardSort.ChangeOwnValue(value.Id, " ", "Something else"); + var changed = cardSort.ChangeOwnValue(value.Id, "Courage", " Speak up "); + + using (Assert.EnterMultipleScope()) + { + Assert.That(emptied, Is.False); + Assert.That(changed, Is.True); + Assert.That(value.Name, Is.EqualTo("Courage")); + Assert.That(value.Definition, Is.EqualTo("Speak up")); + Assert.That(cardSort.ChangeOwnValue("unknown", "Name", string.Empty), Is.False); + } + } + + [Test] + public void ARemovedOwnValueLeavesTheValuesToLookAtMoreClosely() + { + var cardSort = new BflCardSort(); + var value = cardSort.AddOwnValue("Curiosity", string.Empty)!; + cardSort.ChooseFocus(["honesty", value.Id]); + + cardSort.RemoveOwnValue(value.Id); + + using (Assert.EnterMultipleScope()) + { + Assert.That(cardSort.OwnValues, Is.Empty); + Assert.That(cardSort.Focus, Is.EqualTo(new[] { "honesty" })); + } + } + + [Test] + public void AtMostThreeValuesAreLookedAtMoreClosely() + { + var cardSort = new BflCardSort(); + cardSort.ChooseFocus(["honesty", "openness"]); + + cardSort.ChooseFocus(["honesty", "openness", "respect", "care"]); + + using (Assert.EnterMultipleScope()) + { + Assert.That(cardSort.Focus, Has.Count.EqualTo(BflCardSort.MAX_FOCUS)); + Assert.That(cardSort.Focus.Take(2), Is.EqualTo(new[] { "honesty", "openness" })); + } + } + + [Test] + public void ValuesChosenEarlierKeepTheirPlace() + { + var cardSort = new BflCardSort(); + cardSort.ChooseFocus(["openness"]); + + cardSort.ChooseFocus(["honesty", "openness"]); + + Assert.That(cardSort.Focus, Is.EqualTo(new[] { "openness", "honesty" })); + } + + [Test] + public async Task OnlyValuesWhichExistCountAsLookedAtMoreClosely() + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); + var cardSort = new BflCardSort(); + var value = cardSort.AddOwnValue("Curiosity", string.Empty)!; + cardSort.Focus = ["value_of_another_deck", "honesty", "honesty", value.Id, "openness", "respect"]; + + var focus = cardSort.FocusIn(deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(focus, Is.EqualTo(new[] { "honesty", value.Id, "openness" })); + Assert.That(cardSort.NameOf(value.Id, deck), Is.EqualTo("Curiosity")); + Assert.That(cardSort.NameOf("honesty", deck), Is.EqualTo("Honesty")); + Assert.That(cardSort.NameOf("value_of_another_deck", deck), Is.Null); + } + } +} \ No newline at end of file diff --git a/app/Tests/BeyondFactsLab/BflFieldCatalogTests.cs b/app/Tests/BeyondFactsLab/BflFieldCatalogTests.cs index 4701925a7..8139e1bca 100644 --- a/app/Tests/BeyondFactsLab/BflFieldCatalogTests.cs +++ b/app/Tests/BeyondFactsLab/BflFieldCatalogTests.cs @@ -73,6 +73,35 @@ public sealed class BflFieldCatalogTests Assert.That(stepsInOrder, Is.EqualTo(BflStep.All)); } + [Test] + public void TheValuesStepShowsTheThreeSectionsOfLarasDialogueLogicInTheirOrder() + { + var sections = BflFieldCatalog.ForStep(BflStep.VALUES_AND_PERSPECTIVES).Select(field => field.Section).Distinct(); + + Assert.That(sections, Is.EqualTo(new[] { BflFormSection.WHAT_MATTERS_TO_ME, BflFormSection.WHAT_TOUCHES_ME, BflFormSection.WHAT_MOVES_OTHERS })); + } + + [Test] + public void TheFieldsOfASectionFollowEachOtherAndHaveAHeading() + { + using (Assert.EnterMultipleScope()) + { + foreach (var step in BflStep.All) + { + var sections = BflFieldCatalog.ForStep(step).Select(field => field.Section).ToList(); + var runs = sections.Where((section, index) => index == 0 || sections[index - 1] != section).ToList(); + Assert.That(runs, Is.Unique, $"The fields of a section of {step} do not follow each other."); + Assert.That(runs.Where(section => section is not BflFormSection.NONE).Select(section => section.Title), Has.None.Empty, $"A section of {step} has no heading."); + } + } + } + + [Test] + public void OnlyTheSectionAboutWhatMattersHoldsTheCardSort() + { + Assert.That(Enum.GetValues().Where(section => section.HasCardSort), Is.EqualTo(new[] { BflFormSection.WHAT_MATTERS_TO_ME })); + } + [Test] public void EveryStepHasAFormWithATitleAndADescription() { diff --git a/app/Tests/BeyondFactsLab/BflShareModelBuilderTests.cs b/app/Tests/BeyondFactsLab/BflShareModelBuilderTests.cs index 06ba978cf..3df3ebd5f 100644 --- a/app/Tests/BeyondFactsLab/BflShareModelBuilderTests.cs +++ b/app/Tests/BeyondFactsLab/BflShareModelBuilderTests.cs @@ -195,7 +195,39 @@ public sealed class BflShareModelBuilderTests Assert.That(Build(forms: forms, conversations: conversations), Does.Not.Contain("\r")); } - private static string Build(Dictionary? forms = null, IReadOnlyDictionary? workStates = null, IReadOnlyDictionary? results = null, IReadOnlyDictionary>? conversations = null, IReadOnlyList? materials = null, IReadOnlyCollection? researchTopics = null, bool includePersonalDisclosures = false) => BflShareModelBuilder.Build(new BflShareInput( + [Test] + public async Task TheCardSortIsSharedWithTheNamesThePersonSaw() + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); + var forms = Forms(); + var cardSort = new BflCardSort(); + cardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + var curiosity = cardSort.AddOwnValue("Curiosity", "Ask questions")!; + cardSort.ChooseFocus(["honesty", curiosity.Id]); + forms[BflStep.VALUES_AND_PERSPECTIVES].CardSort = cardSort; + + var session = Build(forms: forms, valueDeck: deck); + + using (Assert.EnterMultipleScope()) + { + Assert.That(session, Does.Contain("**Card sort:**\n\n- **Honesty:** Especially important\n")); + Assert.That(session, Does.Contain($"- **Safety:** {BflValueCategory.NONE.Title}\n")); + Assert.That(session, Does.Contain("- **Own values:** Curiosity (Ask questions)\n")); + Assert.That(session, Does.Contain("- **To look at more closely:** Honesty, Curiosity\n")); + } + } + + [Test] + public async Task AnUntouchedCardSortIsNotShared() + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); + var forms = Forms(); + forms[BflStep.VALUES_AND_PERSPECTIVES].CardSort = new(); + + Assert.That(Build(forms: forms, valueDeck: deck), Does.Not.Contain("Card sort")); + } + + private static string Build(Dictionary? forms = null, IReadOnlyDictionary? workStates = null, IReadOnlyDictionary? results = null, IReadOnlyDictionary>? conversations = null, IReadOnlyList? materials = null, IReadOnlyCollection? researchTopics = null, bool includePersonalDisclosures = false, BflValueDeck? valueDeck = null) => BflShareModelBuilder.Build(new BflShareInput( forms ?? Forms(), workStates ?? new Dictionary(), results ?? new Dictionary(), @@ -205,7 +237,8 @@ public sealed class BflShareModelBuilderTests includePersonalDisclosures, "26.11.2", NOW, - CultureInfo.InvariantCulture)); + CultureInfo.InvariantCulture, + valueDeck)); private static Dictionary Forms() => BflStep.All.ToDictionary(step => step, _ => new BflStepFormData()); diff --git a/app/Tests/BeyondFactsLab/BflStalenessTests.cs b/app/Tests/BeyondFactsLab/BflStalenessTests.cs index 95e1789ee..8f427aa0c 100644 --- a/app/Tests/BeyondFactsLab/BflStalenessTests.cs +++ b/app/Tests/BeyondFactsLab/BflStalenessTests.cs @@ -1,3 +1,6 @@ +using System.Security.Cryptography; +using System.Text; + using AIStudio.Assistants.BeyondFactsLab; namespace AIStudio.Tests.BeyondFactsLab; @@ -167,6 +170,62 @@ public sealed class BflStalenessTests Assert.That(BflStaleness.FingerprintOf(first), Is.Not.EqualTo(BflStaleness.FingerprintOf(second))); } + [Test] + public void WithoutACardSortTheFingerprintStaysAsItWasBeforeTheCardSort() + { + var withoutCardSort = new BflStepFormData(); + withoutCardSort.Set(BflFieldCatalog.VALUES_AND_CONCERNS, "Text"); + var withEmptyCardSort = new BflStepFormData { CardSort = new() }; + withEmptyCardSort.Set(BflFieldCatalog.VALUES_AND_CONCERNS, "Text"); + + // How the lab computed the fingerprint of this form before it had a card sort: + var before = Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes($"{BflFieldCatalog.VALUES_AND_CONCERNS}\0Text\0"))); + + using (Assert.EnterMultipleScope()) + { + Assert.That(BflStaleness.FingerprintOf(withoutCardSort), Is.EqualTo(before)); + Assert.That(BflStaleness.FingerprintOf(withEmptyCardSort), Is.EqualTo(before)); + } + } + + [Test] + public void TheFingerprintCountsTheCardSort() + { + var empty = new BflStepFormData(); + var sorted = new BflStepFormData { CardSort = new() }; + sorted.CardSort.Sort("honesty", BflValueCategory.ESPECIALLY_IMPORTANT); + var sortedOtherwise = new BflStepFormData { CardSort = new() }; + sortedOtherwise.CardSort.Sort("honesty", BflValueCategory.UNCLEAR); + var withOwnValue = new BflStepFormData { CardSort = new() }; + withOwnValue.CardSort.AddOwnValue("Curiosity", string.Empty); + + var fingerprints = new[] { empty, sorted, sortedOtherwise, withOwnValue }.Select(BflStaleness.FingerprintOf).ToList(); + + Assert.That(fingerprints, Is.Unique); + } + + [Test] + public void TheOrderOfTheValuesToLookAtMoreCloselyIsNoChange() + { + var first = new BflStepFormData { CardSort = new() { Focus = ["honesty", "openness"] } }; + var second = new BflStepFormData { CardSort = new() { Focus = ["openness", "honesty"] } }; + + Assert.That(BflStaleness.FingerprintOf(first), Is.EqualTo(BflStaleness.FingerprintOf(second))); + } + + [Test] + public void AChangedCardSortWeighsLikeChangedDetails() + { + var form = new BflStepFormData(); + var workStates = new Dictionary(); + Confirm(workStates, BflStep.VALUES_AND_PERSPECTIVES, form); + + form.CardSort = new(); + form.CardSort.Sort("openness", BflValueCategory.ALSO_IMPORTANT); + + Assert.That(BflStaleness.Of(BflStep.VALUES_AND_PERSPECTIVES, workStates, Forms((BflStep.VALUES_AND_PERSPECTIVES, form))), Is.EqualTo(BflWorkStateStatus.DETAILS_CHANGED)); + } + [Test] public void AConfirmationRemembersOnlyEarlierSteps() { diff --git a/app/Tests/BeyondFactsLab/BflValueDeckTests.cs b/app/Tests/BeyondFactsLab/BflValueDeckTests.cs new file mode 100644 index 000000000..3923ec531 --- /dev/null +++ b/app/Tests/BeyondFactsLab/BflValueDeckTests.cs @@ -0,0 +1,100 @@ +using System.Globalization; + +using AIStudio.Assistants.BeyondFactsLab; + +namespace AIStudio.Tests.BeyondFactsLab; + +/// +/// Checks the value deck of the BeyondFacts Lab: that the app comes with it in every language it names, that every +/// language has the same values in the same rounds, and that a deck with a wrong form is refused. +/// +/// +/// The tests run a development build, which reads the decks from its output folder: the project file copies every +/// embedded file there as well. +/// +[TestFixture] +public sealed class BflValueDeckTests +{ + private static IEnumerable Languages() => BflValueDeck.LANGUAGES.Order(StringComparer.Ordinal); + + [TestCaseSource(nameof(Languages))] + public async Task EveryLanguageHasAValidDeck(string language) + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo(language)); + + using (Assert.EnterMultipleScope()) + { + Assert.That(deck.Problems(), Is.Empty); + Assert.That(deck.Cards, Has.Count.EqualTo(BflValueDeck.ROUNDS * BflValueDeck.VALUES_PER_ROUND)); + Assert.That(deck.Citation, Does.Contain("Miller").And.Contain("2001").And.Contain("Personal Values Card Sort")); + } + } + + [Test] + public async Task EveryLanguageHasTheSameValuesInTheSameRounds() + { + var english = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); + foreach (var language in Languages()) + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo(language)); + Assert.That(deck.Rounds.Select(round => round.Select(card => card.Id)), Is.EqualTo(english.Rounds.Select(round => round.Select(card => card.Id))), language); + } + } + + [TestCase("de-DE", "values.de.json")] + [TestCase("de-AT", "values.de.json")] + [TestCase("en-US", "values.en.json")] + [TestCase("fr-FR", "values.en.json")] + [TestCase("", "values.en.json")] + public void TheAppShowsTheDeckOfItsLanguageOrElseTheEnglishOne(string culture, string fileName) + { + Assert.That(BflValueDeck.PathOf(CultureInfo.GetCultureInfo(culture)), Does.EndWith($"/{fileName}")); + } + + [Test] + public async Task AValueIsFoundByItsId() + { + var deck = await BflValueDeck.LoadAsync(CultureInfo.GetCultureInfo("en")); + + using (Assert.EnterMultipleScope()) + { + Assert.That(deck.Find("honesty")?.Name, Is.EqualTo("Honesty")); + Assert.That(deck.Find("unknown"), Is.Null); + } + } + + [Test] + public void ADeckWithAWrongNumberOfRoundsOrValuesIsRefused() + { + var deck = ValidDeck(); + deck.Rounds.RemoveAt(2); + deck.Rounds[0].RemoveAt(3); + + Assert.That(deck.Problems(), Has.Count.EqualTo(2)); + } + + [Test] + public void ADeckWithAnIdTwiceOrAValueWithoutItsTextsIsRefused() + { + var deck = ValidDeck(); + deck.Rounds[1][0].Id = deck.Rounds[0][0].Id; + deck.Rounds[2][0].Definition = " "; + + Assert.That(deck.Problems(), Has.Count.EqualTo(2)); + } + + [Test] + public void ADeckWithoutItsSourceIsRefused() + { + var deck = ValidDeck(); + deck.Citation = string.Empty; + + Assert.That(deck.Problems(), Has.One.Contains("source")); + } + + private static BflValueDeck ValidDeck() => new() + { + Citation = "Miller et al. (2001)", + Rounds = [.. Enumerable.Range(0, BflValueDeck.ROUNDS).Select(round => Enumerable.Range(0, BflValueDeck.VALUES_PER_ROUND).Select(index => new BflValueCard { Id = $"value_{round}_{index}", Name = "Name", Definition = "Definition" }).ToList())], + }; +} \ No newline at end of file diff --git a/app/Tests/Serialization/JsonTestRoots.cs b/app/Tests/Serialization/JsonTestRoots.cs index 3f3ee1dda..22cbe9476 100644 --- a/app/Tests/Serialization/JsonTestRoots.cs +++ b/app/Tests/Serialization/JsonTestRoots.cs @@ -71,6 +71,9 @@ public static partial class JsonTestRoots yield return Case(new(typeof(BflResearch), BeyondFactsLabJson.PERSISTENCE)); yield return Case(new(typeof(Guid?), BeyondFactsLabJson.PERSISTENCE), "selected preparation"); + // The value deck of the BeyondFacts Lab, which comes with the app and is read like its files: + yield return Case(new(typeof(BflValueDeck), BeyondFactsLabJson.PERSISTENCE), "value deck"); + // The presets and the stored results of the Model Comparison assistant: yield return Case(new(typeof(ModelComparisonPreset), ModelComparisonJson.PERSISTENCE)); yield return Case(new(typeof(ModelComparisonRecord), ModelComparisonJson.PERSISTENCE));