From da513d423bb018f8dbe3246ff25254c9eef63f4e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 31 Aug 2025 14:14:19 +0200 Subject: [PATCH] Added support for citations to the chat interface --- app/MindWork AI Studio/Assistants/I18N/allTexts.lua | 6 ++++++ .../Chat/ContentBlockComponent.razor | 13 +++++++++++++ app/MindWork AI Studio/Chat/ContentImage.cs | 3 +++ app/MindWork AI Studio/Chat/ContentText.cs | 12 +++++++++--- app/MindWork AI Studio/Chat/IContent.cs | 6 ++++++ .../plugin.lua | 6 ++++++ .../plugin.lua | 6 ++++++ app/MindWork AI Studio/Provider/SourceExtensions.cs | 12 ++++++++++++ app/MindWork AI Studio/wwwroot/app.css | 5 +++++ app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md | 3 ++- 10 files changed, 68 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 9c21ef03..802feec6 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -1333,6 +1333,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, re -- Yes, remove it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it" +-- Number of sources +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Number of sources" + -- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted." @@ -4861,6 +4864,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" +-- Sources +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SOURCEEXTENSIONS::T2730980305"] = "Sources" + -- Use no chat template UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template" diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor index f7d67759..e1cfcb90 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor @@ -1,6 +1,7 @@ @using AIStudio.Tools @using MudBlazor @using AIStudio.Components +@using AIStudio.Provider @inherits AIStudio.Components.MSGComponentBase @@ -15,6 +16,14 @@ + @if (this.Content.Sources.Count > 0) + { + + + + + + } @if (this.IsSecondToLastBlock && this.Role is ChatRole.USER && this.EditLastUserBlockFunc is not null) { @@ -72,6 +81,10 @@ else { + @if (textContent.Sources.Count > 0) + { + + } } } } diff --git a/app/MindWork AI Studio/Chat/ContentImage.cs b/app/MindWork AI Studio/Chat/ContentImage.cs index 6fcf7f1e..f37ba652 100644 --- a/app/MindWork AI Studio/Chat/ContentImage.cs +++ b/app/MindWork AI Studio/Chat/ContentImage.cs @@ -27,6 +27,9 @@ public sealed class ContentImage : IContent, IImageSource [JsonIgnore] public Func StreamingEvent { get; set; } = () => Task.CompletedTask; + /// + public List Sources { get; set; } = []; + /// public Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatChatThread, CancellationToken token = default) { diff --git a/app/MindWork AI Studio/Chat/ContentText.cs b/app/MindWork AI Studio/Chat/ContentText.cs index 590bef35..a5f0ef4f 100644 --- a/app/MindWork AI Studio/Chat/ContentText.cs +++ b/app/MindWork AI Studio/Chat/ContentText.cs @@ -24,7 +24,7 @@ public sealed class ContentText : IContent public bool InitialRemoteWait { get; set; } /// - // [JsonIgnore] + [JsonIgnore] public bool IsStreaming { get; set; } /// @@ -35,6 +35,9 @@ public sealed class ContentText : IContent [JsonIgnore] public Func StreamingEvent { get; set; } = () => Task.CompletedTask; + /// + public List Sources { get; set; } = []; + /// public async Task CreateFromProviderAsync(IProvider provider, Model chatModel, IContent? lastPrompt, ChatThread? chatThread, CancellationToken token = default) { @@ -80,7 +83,7 @@ public sealed class ContentText : IContent this.InitialRemoteWait = true; // Iterate over the responses from the AI: - await foreach (var deltaText in provider.StreamChatCompletion(chatModel, chatThread, settings, token)) + await foreach (var contentStreamChunk in provider.StreamChatCompletion(chatModel, chatThread, settings, token)) { // When the user cancels the request, we stop the loop: if (token.IsCancellationRequested) @@ -91,7 +94,10 @@ public sealed class ContentText : IContent this.IsStreaming = true; // Add the response to the text: - this.Text += deltaText; + this.Text += contentStreamChunk; + + // Merge the sources: + this.Sources.MergeSources(contentStreamChunk.Sources); // Notify the UI that the content has changed, // depending on the energy saving mode: diff --git a/app/MindWork AI Studio/Chat/IContent.cs b/app/MindWork AI Studio/Chat/IContent.cs index c03f6574..fc71f760 100644 --- a/app/MindWork AI Studio/Chat/IContent.cs +++ b/app/MindWork AI Studio/Chat/IContent.cs @@ -37,6 +37,12 @@ public interface IContent /// [JsonIgnore] public Func StreamingDone { get; set; } + + /// + /// The provided sources, if any. + /// + [JsonIgnore] + public List Sources { get; set; } /// /// Uses the provider to create the content. 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 ca1832b3..d5dde56a 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 @@ -1335,6 +1335,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Ja, neu -- Yes, remove it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Ja, entferne es" +-- Number of sources +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Anzahl der Quellen" + -- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Möchten Sie diese Nachricht wirklich bearbeiten? Um die Nachricht zu bearbeiten, wird die Antwort der KI gelöscht." @@ -4863,6 +4866,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "Kein Modell ausgewählt" +-- Sources +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SOURCEEXTENSIONS::T2730980305"] = "Quellen" + -- Use no chat template UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Keine Chat-Vorlage verwenden" 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 a4d628c2..1b3afed6 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 @@ -1335,6 +1335,9 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, re -- Yes, remove it UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it" +-- Number of sources +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1848978959"] = "Number of sources" + -- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted." @@ -4863,6 +4866,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un -- no model selected UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" +-- Sources +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SOURCEEXTENSIONS::T2730980305"] = "Sources" + -- Use no chat template UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template" diff --git a/app/MindWork AI Studio/Provider/SourceExtensions.cs b/app/MindWork AI Studio/Provider/SourceExtensions.cs index a99f213e..ce208612 100644 --- a/app/MindWork AI Studio/Provider/SourceExtensions.cs +++ b/app/MindWork AI Studio/Provider/SourceExtensions.cs @@ -32,4 +32,16 @@ public static class SourceExtensions return sb.ToString(); } + + /// + /// Merges a list of added sources into an existing list of sources, avoiding duplicates based on URL and Title. + /// + /// The existing list of sources to merge into. + /// The list of sources to add. + public static void MergeSources(this IList sources, IList addedSources) + { + foreach (var addedSource in addedSources) + if (sources.All(s => s.URL != addedSource.URL && s.Title != addedSource.Title)) + sources.Add((Source)addedSource); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/app.css b/app/MindWork AI Studio/wwwroot/app.css index a0d9e860..07572473 100644 --- a/app/MindWork AI Studio/wwwroot/app.css +++ b/app/MindWork AI Studio/wwwroot/app.css @@ -140,4 +140,9 @@ .no-elevation { box-shadow: none !important; +} + +.sources-card-header { + top: 0em !important; + left: 2.2em !important; } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md index f810950e..bbff8e6b 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.51.md @@ -2,7 +2,8 @@ - Added support for predefined chat templates in configuration plugins to help enterprises roll out consistent templates across the organization. - Added the ability to choose between automatic and manual update installation to the app settings (default is manual). - Added the ability to control the update installation behavior by configuration plugins. -- Added the option for LLM providers to return citations. +- Added the option for LLM providers to stream citations or sources. +- Added support for citations to the chat interface. This feature is invisible unless an LLM model is streaming citations or sources. - Improved memory usage in several areas of the app. - Improved plugin management for configuration plugins so that hot reload detects when a provider or chat template has been removed. - Improved the dialog for naming chats and workspaces to ensure valid inputs are entered.