diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs index 73821e83..17ad23de 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingArtifactService.Assembly.cs @@ -328,9 +328,7 @@ public sealed partial class VisualBriefingArtifactService .GroupBy(contribution => contribution.Model, StringComparer.Ordinal) .Select(group => { - var roles = group.Select(contribution => contribution.Role) - .Distinct() - .Select(role => role is VisualBriefingModelRole.DESIGN ? "presentation" : "content"); + var roles = group.Select(contribution => contribution.Role is VisualBriefingModelRole.DESIGN ? "presentation" : "content").Distinct(StringComparer.Ordinal); return $"{group.Key} ({string.Join(", ", roles)})"; })); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.cs index 5adf277f..3a02cc20 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingBuildOrchestrator.cs @@ -382,7 +382,7 @@ internal sealed partial class VisualBriefingBuildOrchestrator }; var revision = await this.store.AddRevisionAsync(new(manifest.BriefingId, parentRevisionId, mode, manifest.Settings.Instruction, - compiled.Data, compiled.TemplateHtml, compiled.Css, VisualBriefingModelNames.ExportLabel(provider.Model), "MindWork AI Studio", + compiled.Data, compiled.TemplateHtml, compiled.Css, VisualBriefingModelNames.ExportLabel(provider), "MindWork AI Studio", content.ArtifactId, presentation.ArtifactId, build.BuildId, build.OperationId, contributions, revisionId, revisionCreatedAt, embeddedAssets, content.AssetPlan, evidence.ArtifactId, plan.ArtifactId), token); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs index 6ef25782..1bb157a5 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingContentStage.cs @@ -51,7 +51,7 @@ internal sealed class VisualBriefingContentStage(StructuredLlmStageRunner stageR artifact.CreatedAtUtc = DateTimeOffset.UtcNow; artifact.SourceCoverage = evidence.SourceCoverage; artifact.StructuralSignature = plan.StructuralSignature; - artifact.Model = VisualBriefingModelNames.ExportLabel(provider.Model); + artifact.Model = VisualBriefingModelNames.ExportLabel(provider); artifact.Data = JsonSerializer.SerializeToElement(new { slots = artifact.Slots.ToDictionary(slot => slot.SlotId, slot => slot.Value, StringComparer.Ordinal), diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs index 6a452c41..bdcd4574 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingEvidenceStage.cs @@ -63,7 +63,7 @@ internal sealed class VisualBriefingEvidenceStage(StructuredLlmStageRunner stage Tables = response.Tables, SourceCoverage = response.SourceCoverage, AssetPlan = response.AssetPlan, - Model = VisualBriefingModelNames.ExportLabel(provider.Model), + Model = VisualBriefingModelNames.ExportLabel(provider), }; await store.WriteEvidenceArtifactAsync(manifest.BriefingId, artifact, token); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingModelNames.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingModelNames.cs index 08029d33..8e620cfb 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingModelNames.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingModelNames.cs @@ -1,17 +1,46 @@ using AIStudio.Provider; +using ProviderSettings = AIStudio.Settings.Provider; + namespace AIStudio.Assistants.VisualBriefing; /// -/// Produces export-safe model labels without exposing provider model identifiers. +/// Produces export-safe provider and model labels. /// internal static class VisualBriefingModelNames { /// - /// Returns the configured display name or a neutral fallback. + /// Returns the public provider family and configured model name. /// - /// The selected provider model. - /// An export-safe model label. - internal static string ExportLabel(Model model) => - string.IsNullOrWhiteSpace(model.DisplayName) ? "Model" : model.DisplayName.Trim(); + /// The selected provider and model. + /// An export-safe provider and model label. + internal static string ExportLabel(ProviderSettings provider) => $"{provider.UsedLLMProvider.ToName(translate: false)} — {ExportModelName(provider.Model)}"; + + /// + /// Reconstructs an export label from persisted build provenance. + /// + /// The persisted provider family. + /// The persisted model name. + /// An export-safe provider and model label. + internal static string ExportLabel(string providerFamily, string model) + { + var providerName = Enum.TryParse(providerFamily, out var parsedProvider) ? parsedProvider.ToName(translate: false) : string.IsNullOrWhiteSpace(providerFamily) ? "Unknown provider" : providerFamily.Trim(); + var modelName = string.IsNullOrWhiteSpace(model) ? "model not reported" : model.Trim(); + + return $"{providerName} — {modelName}"; + } + + /// + /// Returns the configured display name, model ID, or provider-managed fallback. + /// + private static string ExportModelName(Model model) + { + if (!string.IsNullOrWhiteSpace(model.DisplayName)) + return model.DisplayName.Trim(); + + if (model.IsSystemModel) + return "provider-configured model"; + + return string.IsNullOrWhiteSpace(model.Id) ? "model not reported" : model.Id.Trim(); + } } diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs index 34e61f54..465c04ce 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPlanStage.cs @@ -64,7 +64,7 @@ internal sealed class VisualBriefingPlanStage(StructuredLlmStageRunner stageRunn PayloadHash = VisualBriefingHashing.ComputeSections(payload, structuralSignature), Sections = sections, StructuralSignature = structuralSignature, - Model = VisualBriefingModelNames.ExportLabel(provider.Model), + Model = VisualBriefingModelNames.ExportLabel(provider), }; await store.WritePlanArtifactAsync(manifest.BriefingId, artifact, token); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs index 11bd49bb..e4646eb7 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingPresentationStage.cs @@ -97,7 +97,7 @@ internal sealed class VisualBriefingPresentationStage(StructuredLlmStageRunner s Css = compiled.Css, TemplateHash = compiled.TemplateHash, CssHash = compiled.CssHash, - Model = VisualBriefingModelNames.ExportLabel(provider.Model), + Model = VisualBriefingModelNames.ExportLabel(provider), }; await store.WritePresentationArtifactAsync(manifest.BriefingId, artifact, token); diff --git a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Recovery.cs b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Recovery.cs index 760c4635..006bc43b 100644 --- a/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Recovery.cs +++ b/app/MindWork AI Studio/Assistants/VisualBriefing/VisualBriefingStore.Recovery.cs @@ -194,18 +194,19 @@ public sealed partial class VisualBriefingStore if (build is null || string.IsNullOrWhiteSpace(build.Model)) return []; + var model = VisualBriefingModelNames.ExportLabel(build.ProviderFamily, build.Model); List contributions = []; if (build.EvidenceArtifactId is not null) - contributions.Add(new(VisualBriefingModelRole.EVIDENCE, build.Model)); + contributions.Add(new(VisualBriefingModelRole.EVIDENCE, model)); if (build.PlanArtifactId is not null) - contributions.Add(new(VisualBriefingModelRole.PLAN, build.Model)); + contributions.Add(new(VisualBriefingModelRole.PLAN, model)); if (build.ContentArtifactId is not null) - contributions.Add(new(VisualBriefingModelRole.CONTENT, build.Model)); + contributions.Add(new(VisualBriefingModelRole.CONTENT, model)); if (build.PresentationArtifactId is not null) - contributions.Add(new(VisualBriefingModelRole.DESIGN, build.Model)); + contributions.Add(new(VisualBriefingModelRole.DESIGN, model)); return contributions; } diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index f4e34844..92a7860d 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -35,9 +35,17 @@ public static class LLMProvidersExtensions /// /// The provider. /// The human-readable name of the provider. - public static string ToName(this LLMProviders llmProvider) => llmProvider switch + public static string ToName(this LLMProviders llmProvider) => llmProvider.ToName(translate: true); + + /// + /// Returns the human-readable name of the provider. + /// + /// The provider. + /// Whether generic provider names should be translated. + /// The human-readable name of the provider. + public static string ToName(this LLMProviders llmProvider, bool translate) => llmProvider switch { - LLMProviders.NONE => TB("No provider selected"), + LLMProviders.NONE => translate ? TB("No provider selected") : "No provider selected", LLMProviders.OPEN_AI => "OpenAI", LLMProviders.ANTHROPIC => "Anthropic", @@ -53,12 +61,12 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => "Fireworks.ai", LLMProviders.HUGGINGFACE => "Hugging Face", - LLMProviders.SELF_HOSTED => TB("Self-hosted"), + LLMProviders.SELF_HOSTED => translate ? TB("Self-hosted") : "Self-hosted", LLMProviders.HELMHOLTZ => "Helmholtz Blablador", LLMProviders.GWDG => "GWDG SAIA", - _ => TB("Unknown"), + _ => translate ? TB("Unknown") : "Unknown", }; ///