mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-10 21:47:21 +00:00
Added an overview of all files attached to a chat (#1002)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
31862f5ecd
commit
9d1768e2f4
10 files changed
+281
-15
No files matched your search
@@ -4132,6 +4132,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3996190985"] = "{0} of {1
|
||||
-- Start temporary chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Start temporary chat"
|
||||
|
||||
-- Show all files attached to this chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4183453875"] = "Show all files attached to this chat"
|
||||
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||
|
||||
@@ -9154,6 +9157,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Canc
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Embeddings"
|
||||
|
||||
-- Version from {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1647139243"] = "Version from {0}"
|
||||
|
||||
-- Attached {0} files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1736997462"] = "Attached {0} files."
|
||||
|
||||
@@ -9169,6 +9175,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2111340711"] = "Th
|
||||
-- You can drag more files into this window to attach them right away.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2653077974"] = "You can drag more files into this window to attach them right away."
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2713115684"] = "Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name."
|
||||
|
||||
-- Document Preview
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T285154968"] = "Document Preview"
|
||||
|
||||
|
||||
@@ -724,6 +724,39 @@ public sealed record ChatThread
|
||||
this.Draft.ReplaceAttachments(replace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether one of the messages the chat shows went with attached files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Asked with every rendering of the chat, so it stops at the first such message instead of
|
||||
/// collecting them, cf. CollectSentFileAttachments.
|
||||
/// </remarks>
|
||||
public bool HasSentFileAttachments() => this.BlocksShownToTheUser().Any(block => block.Content!.FileAttachments.Count > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Collects the files attached to the messages the chat shows, once for each version of a file that was sent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every message keeps the text its documents were sent with, see FileAttachment.ProvenanceId, and
|
||||
/// the AI receives all of these texts with every request. A file which changed between two messages
|
||||
/// therefore reached the AI in two versions, and both are collected; the state of the file their
|
||||
/// texts were read from, FileAttachment.Version, tells them apart. A file sent again unchanged is
|
||||
/// collected once, with the entry of its latest message. Attachments without a noted state, such
|
||||
/// as images and documents which every request reads anew, are told apart by their path alone.
|
||||
/// The draft is left out, since the paperclip shows it, and so are the messages the chat hides.
|
||||
/// </remarks>
|
||||
/// <returns>The attachments, each version of a file once.</returns>
|
||||
public HashSet<FileAttachment> CollectSentFileAttachments() => this.BlocksShownToTheUser()
|
||||
.OrderByDescending(block => block.Time)
|
||||
.SelectMany(block => block.Content!.FileAttachments)
|
||||
.DistinctBy(attachment => (attachment.FilePath, attachment.Version))
|
||||
.ToHashSet();
|
||||
|
||||
/// <summary>
|
||||
/// The messages the chat shows: the same ones the chat component renders.
|
||||
/// </summary>
|
||||
private IEnumerable<ContentBlock> BlocksShownToTheUser() => this.Blocks.Where(block => block is { HideFromUser: false, Content: not null });
|
||||
|
||||
/// <summary>
|
||||
/// Replaces every file path the chat stores with what the given function makes of it.
|
||||
/// </summary>
|
||||
|
||||
@@ -114,6 +114,10 @@
|
||||
|
||||
<AttachDocuments Name="File Attachments" DocumentPaths="@this.ComposerState.FileAttachments" DocumentPathsChanged="@this.ComposerAttachmentsChanged" CatchAllDocuments="true" UseSmallForm="true" ShowMediaStatus="false" Provider="@this.Provider" OwnerChat="@this.ChatThread" EnsureOwnerChatAsync="@this.EnsureMediaImportChatAsync" KeepPreviewedText="@this.KeepPreviewedTextAsync" Disabled="@(!this.CanAttachFiles)"/>
|
||||
|
||||
<MudTooltip Text="@T("Show all files attached to this chat")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.LibraryBooks" Disabled="@(!this.HasSentFileAttachments)" OnClick="@this.ShowSentFileAttachments"/>
|
||||
</MudTooltip>
|
||||
|
||||
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||
|
||||
<MudTooltip Text="@T("Bold")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
|
||||
@@ -850,6 +850,22 @@ public partial class ChatComponent : MSGComponentBase
|
||||
&& !this.MediaTranscriptionService.IsBusy(this.CurrentMediaImportOwner)
|
||||
&& WorkspaceBehaviour.IsChatExisting(new LoadChat(this.ChatThread.WorkspaceId, this.ChatThread.ChatId));
|
||||
|
||||
private bool HasSentFileAttachments => this.ChatThread?.HasSentFileAttachments() ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the files of all sent messages, each version of a file once, see ChatThread.CollectSentFileAttachments.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Without removing: the overview gathers several messages, and a removal could not tell which one it belongs to.
|
||||
/// </remarks>
|
||||
private async Task ShowSentFileAttachments()
|
||||
{
|
||||
if (this.ChatThread is null)
|
||||
return;
|
||||
|
||||
await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.ChatThread.CollectSentFileAttachments(), canRemove: false);
|
||||
}
|
||||
|
||||
private string TooltipAddChatToWorkspace => string.Format(T("Start new chat in workspace '{0}'"), this.currentWorkspaceName);
|
||||
|
||||
private string UserInputStyle => this.SettingsManager.ConfigurationData.Confidence.ShowProviderConfidence ? this.Provider.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty;
|
||||
|
||||
@@ -9,7 +9,14 @@
|
||||
Disabled="@this.IsZoneDisabled"
|
||||
Context="isDropTarget">
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@T("Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon.")
|
||||
@if (this.CanRemove)
|
||||
{
|
||||
@T("Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon.")
|
||||
}
|
||||
else
|
||||
{
|
||||
@T("Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name.")
|
||||
}
|
||||
</MudJustifiedText>
|
||||
|
||||
@if (this.CanAttach)
|
||||
@@ -31,8 +38,10 @@
|
||||
|
||||
@{
|
||||
var currentFolder = string.Empty;
|
||||
var pathsWithSeveralVersions = this.PathsWithSeveralVersions;
|
||||
foreach (var fileAttachment in this.OrderedAttachments)
|
||||
{
|
||||
var versionLabel = this.VersionLabel(fileAttachment, pathsWithSeveralVersions);
|
||||
var folderPath = Path.GetDirectoryName(fileAttachment.FilePath);
|
||||
if (folderPath != currentFolder)
|
||||
{
|
||||
@@ -55,6 +64,12 @@
|
||||
<MudText Style="white-space: nowrap;">
|
||||
@fileAttachment.FileName
|
||||
</MudText>
|
||||
@if (versionLabel is not null)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="ml-2 mud-text-secondary" Style="white-space: nowrap;">
|
||||
@versionLabel
|
||||
</MudText>
|
||||
}
|
||||
</span>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
@@ -66,11 +81,14 @@
|
||||
OnClick="@(() => this.InvestigateFile(fileAttachment))"/>
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Remove this attachment.")" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => this.DeleteAttachment(fileAttachment))"/>
|
||||
</MudTooltip>
|
||||
@if (this.CanRemove)
|
||||
{
|
||||
<MudTooltip Text="@T("Remove this attachment.")" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => this.DeleteAttachment(fileAttachment))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudToolBar>
|
||||
</MudStack>
|
||||
}
|
||||
@@ -84,6 +102,12 @@
|
||||
<MudText Style="white-space: nowrap;">
|
||||
<s>@fileAttachment.FileName</s>
|
||||
</MudText>
|
||||
@if (versionLabel is not null)
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="ml-2 mud-text-secondary" Style="white-space: nowrap;">
|
||||
@versionLabel
|
||||
</MudText>
|
||||
}
|
||||
</span>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
@@ -105,11 +129,14 @@
|
||||
Disabled="true"/>
|
||||
}
|
||||
|
||||
<MudTooltip Text="@T("Remove this attachment.")" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => this.DeleteAttachment(fileAttachment))"/>
|
||||
</MudTooltip>
|
||||
@if (this.CanRemove)
|
||||
{
|
||||
<MudTooltip Text="@T("Remove this attachment.")" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => this.DeleteAttachment(fileAttachment))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudToolBar>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ public partial class ReviewAttachmentsDialog : MSGComponentBase
|
||||
[Parameter]
|
||||
public Func<FileAttachment, string, FileVersion, Task>? KeepReadText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user may remove attachments here.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// False when this dialog gathers the attachments of several messages at once: a removal would
|
||||
/// reach into a message that was already sent, and nothing here could tell which one.
|
||||
/// </remarks>
|
||||
[Parameter]
|
||||
public bool CanRemove { get; set; } = true;
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; set; } = null!;
|
||||
|
||||
@@ -83,7 +93,7 @@ public partial class ReviewAttachmentsDialog : MSGComponentBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The attachments, sorted by their folder and, within it, by their file name.
|
||||
/// The attachments, sorted by their folder and, within it, by their file name, the latest version of a file first.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The list below starts a new heading whenever the folder changes from one attachment to the
|
||||
@@ -95,7 +105,37 @@ public partial class ReviewAttachmentsDialog : MSGComponentBase
|
||||
/// </remarks>
|
||||
private IEnumerable<FileAttachment> OrderedAttachments => this.DocumentPaths
|
||||
.OrderBy(attachment => Path.GetDirectoryName(attachment.FilePath) ?? string.Empty, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(attachment => attachment.FileName, StringComparer.OrdinalIgnoreCase);
|
||||
.ThenBy(attachment => attachment.FileName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenByDescending(attachment => attachment.Version?.LastWriteUtc);
|
||||
|
||||
/// <summary>
|
||||
/// The paths which the list holds more than once, i.e., the files which were sent in several versions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only the overview of all files of a chat holds a file more than once, see
|
||||
/// ChatThread.CollectSentFileAttachments: the draft and a single message hold each file once, see
|
||||
/// SetFileAttachmentExtensions.AttachInPlaceOfSameFile. The other uses of this dialog therefore
|
||||
/// stay as they were. Asked once for each rendering, not once for each attachment.
|
||||
/// </remarks>
|
||||
private IReadOnlySet<string> PathsWithSeveralVersions => this.DocumentPaths
|
||||
.GroupBy(attachment => attachment.FilePath)
|
||||
.Where(group => group.Count() > 1)
|
||||
.Select(group => group.Key)
|
||||
.ToHashSet();
|
||||
|
||||
/// <summary>
|
||||
/// Tells apart the versions of a file which the list holds more than once, by the time their file was saved.
|
||||
/// </summary>
|
||||
/// <param name="attachment">The attachment.</param>
|
||||
/// <param name="pathsWithSeveralVersions">The paths which the list holds more than once, see PathsWithSeveralVersions.</param>
|
||||
/// <returns>The label, or null for a file listed once, and for a version whose state AI Studio did not note.</returns>
|
||||
private string? VersionLabel(FileAttachment attachment, IReadOnlySet<string> pathsWithSeveralVersions)
|
||||
{
|
||||
if (attachment.Version is not { } version || !pathsWithSeveralVersions.Contains(attachment.FilePath))
|
||||
return null;
|
||||
|
||||
return string.Format(T("Version from {0}"), CommonTools.FormatTimestampToGeneral(version.LastWriteUtc.ToLocalTime(), I18N.I.Culture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches what the user dropped onto this dialog and answers which files that became.
|
||||
@@ -129,11 +169,12 @@ public partial class ReviewAttachmentsDialog : MSGComponentBase
|
||||
|
||||
private async Task PathsDropped(List<string> paths) => await this.AttachPathsAsync(paths);
|
||||
|
||||
public static async Task<HashSet<FileAttachment>> OpenDialogAsync(IDialogService dialogService, HashSet<FileAttachment> documentPaths, Func<List<string>, Task<IReadOnlyList<FileAttachment>>>? attachPaths = null, Func<bool>? isAttachingUnavailable = null, Func<FileAttachment, string, FileVersion, Task>? keepReadText = null)
|
||||
public static async Task<HashSet<FileAttachment>> OpenDialogAsync(IDialogService dialogService, HashSet<FileAttachment> documentPaths, Func<List<string>, Task<IReadOnlyList<FileAttachment>>>? attachPaths = null, Func<bool>? isAttachingUnavailable = null, Func<FileAttachment, string, FileVersion, Task>? keepReadText = null, bool canRemove = true)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ReviewAttachmentsDialog>
|
||||
{
|
||||
{ x => x.DocumentPaths, documentPaths }
|
||||
{ x => x.DocumentPaths, documentPaths },
|
||||
{ x => x.CanRemove, canRemove },
|
||||
};
|
||||
|
||||
if (attachPaths is not null)
|
||||
|
||||
+9
@@ -4134,6 +4134,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3996190985"] = "{0} von {
|
||||
-- Start temporary chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Neuen selbstlöschenden Chat starten"
|
||||
|
||||
-- Show all files attached to this chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4183453875"] = "Alle an diesen Chat angehängten Dateien anzeigen"
|
||||
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Überschrift"
|
||||
|
||||
@@ -9156,6 +9159,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Abbr
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Einbettungen"
|
||||
|
||||
-- Version from {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1647139243"] = "Version vom {0}"
|
||||
|
||||
-- Attached {0} files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1736997462"] = "{0} Dateien angehängt."
|
||||
|
||||
@@ -9171,6 +9177,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2111340711"] = "De
|
||||
-- You can drag more files into this window to attach them right away.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2653077974"] = "Sie können weitere Dateien in dieses Fenster ziehen, um sie sofort anzuhängen."
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2713115684"] = "Hier sehen Sie alle angehängten Dateien. Dateien, die nicht mehr gefunden werden können (gelöscht, umbenannt oder verschoben), sind mit einem Warnsymbol und einem durchgestrichenen Namen markiert."
|
||||
|
||||
-- Document Preview
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T285154968"] = "Dokumentvorschau"
|
||||
|
||||
|
||||
+9
@@ -4134,6 +4134,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3996190985"] = "{0} of {1
|
||||
-- Start temporary chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "New disappearing chat"
|
||||
|
||||
-- Show all files attached to this chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4183453875"] = "Show all files attached to this chat"
|
||||
|
||||
-- Heading
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||
|
||||
@@ -9156,6 +9159,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Canc
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Embeddings"
|
||||
|
||||
-- Version from {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1647139243"] = "Version from {0}"
|
||||
|
||||
-- Attached {0} files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1736997462"] = "Attached {0} files."
|
||||
|
||||
@@ -9171,6 +9177,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2111340711"] = "Th
|
||||
-- You can drag more files into this window to attach them right away.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2653077974"] = "You can drag more files into this window to attach them right away."
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2713115684"] = "Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name."
|
||||
|
||||
-- Document Preview
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T285154968"] = "Document Preview"
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- Added a way for IT departments to preselect the options of the Prompt Optimizer, such as the prompt guide of your organization, the language of the optimized prompt, and the provider. They can lock each option or let you change it.
|
||||
- Added your own prompt guide to the options of the Prompt Optimizer. Choose the file once, and the assistant uses it whenever you open it or start over, always with the current content of the file.
|
||||
- Added a way to see the complete system prompt of a chat: the button with the eye in the chat toolbar shows the instructions the AI receives ahead of your conversation, part by part, such as your chat template, your profile, data from your data sources, and the instructions of your tools. You can copy each part or the complete prompt. Thanks, Jens Erler (`j-erler`), for this contribution.
|
||||
- Added an overview of all files you sent in a chat: the button next to the paperclip in the chat toolbar lists the files of all your messages, grouped by their folder. When you sent a file in several versions, the overview lists each of them with the time the file was saved. You can read the text the AI received from each file, even when you moved or deleted the file since. Thanks, Jens Erler (`j-erler`), for this contribution.
|
||||
- Improved the documentation for IT departments, which now lives in our [wiki](https://github.com/MindWorkAI/AI-Studio/wiki/Enterprise-IT). It is sorted by task, explains the setup on Windows, Linux, and macOS, and offers complete configurations for typical needs, such as using only local models. The link in the app settings leads there, and the wiki also holds the installation guide in English and German.
|
||||
- Improved the German translation throughout: we proofread all texts and corrected wrong labels as well as spelling and grammar mistakes. The texts now always address you formally and use the same terms everywhere.
|
||||
- Improved the security of the connection between the app window and AI Studio.
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Tests.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// Checks which attachments the overview of all files of a chat lists.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every message keeps the text its documents were sent with. A file changed between two messages
|
||||
/// reached the AI in two versions, and the overview has to show both, while a file sent again
|
||||
/// unchanged is one version only.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class ChatThreadSentFileAttachmentsTests
|
||||
{
|
||||
private static readonly DateTimeOffset START = new(2026, 10, 16, 9, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private static readonly string MINUTES_PATH = Path.Combine(Path.GetTempPath(), "board", "minutes.txt");
|
||||
|
||||
private static readonly FileVersion FIRST_VERSION = new(new DateTime(2026, 10, 15, 8, 0, 0, DateTimeKind.Utc), 120);
|
||||
|
||||
private static readonly FileVersion SECOND_VERSION = new(new DateTime(2026, 11, 5, 17, 30, 0, DateTimeKind.Utc), 180);
|
||||
|
||||
[Test]
|
||||
public void AFileChangedBetweenTwoMessagesAppearsInBothVersions()
|
||||
{
|
||||
var first = Minutes(FIRST_VERSION);
|
||||
var second = Minutes(SECOND_VERSION);
|
||||
var thread = Thread(Message(1, first), Message(2, second));
|
||||
|
||||
var attachments = thread.CollectSentFileAttachments();
|
||||
|
||||
Assert.That(attachments, Is.EquivalentTo([first, second]), "The AI received both versions, so the overview has to show both.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AFileSentAgainUnchangedAppearsOnceWithItsLatestMessage()
|
||||
{
|
||||
var earlier = Minutes(FIRST_VERSION);
|
||||
var later = Minutes(FIRST_VERSION);
|
||||
|
||||
// The chat orders its messages by their time, not by their place in the list:
|
||||
var thread = Thread(Message(2, later), Message(1, earlier));
|
||||
|
||||
var attachments = thread.CollectSentFileAttachments();
|
||||
|
||||
Assert.That(attachments, Is.EquivalentTo([later]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnAttachmentWithoutANotedStateAppearsOncePerPath()
|
||||
{
|
||||
var imagePath = Path.Combine(Path.GetTempPath(), "board", "chart.png");
|
||||
var thread = Thread(Message(1, new FileAttachmentImage("chart.png", imagePath, 2048)), Message(2, new FileAttachmentImage("chart.png", imagePath, 4096)));
|
||||
|
||||
var attachments = thread.CollectSentFileAttachments();
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(attachments, Has.Count.EqualTo(1), "Every request reads such a file anew, so there is only one version of it.");
|
||||
Assert.That(attachments.Single().FileSizeBytes, Is.EqualTo(4096), "The latest message names the file as it was last.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheDraftAndTheHiddenMessagesAreLeftOut()
|
||||
{
|
||||
var thread = Thread(HiddenMessage(1, Minutes(FIRST_VERSION)), Message(2));
|
||||
thread.Draft.MirrorAttachments([Minutes(SECOND_VERSION)]);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(thread.HasSentFileAttachments(), Is.False);
|
||||
Assert.That(thread.CollectSentFileAttachments(), Is.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AMessageWithAFileCountsAsSent()
|
||||
{
|
||||
var thread = Thread(Message(1), Message(2, Minutes(FIRST_VERSION)));
|
||||
Assert.That(thread.HasSentFileAttachments(), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AChatWithoutAttachmentsHasNone()
|
||||
{
|
||||
var thread = Thread(Message(1), Message(2));
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(thread.HasSentFileAttachments(), Is.False);
|
||||
Assert.That(thread.CollectSentFileAttachments(), Is.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static FileAttachment Minutes(FileVersion version) => new(FileAttachmentType.DOCUMENT, "minutes.txt", MINUTES_PATH, version.Length)
|
||||
{
|
||||
ProvenanceId = Guid.NewGuid(),
|
||||
Version = version,
|
||||
};
|
||||
|
||||
private static ChatThread Thread(params ContentBlock[] blocks) => new() { Blocks = [..blocks] };
|
||||
|
||||
private static ContentBlock Message(int minute, params FileAttachment[] attachments) => Block(minute, false, attachments);
|
||||
|
||||
private static ContentBlock HiddenMessage(int minute, params FileAttachment[] attachments) => Block(minute, true, attachments);
|
||||
|
||||
private static ContentBlock Block(int minute, bool hidden, FileAttachment[] attachments) => new()
|
||||
{
|
||||
Time = START.AddMinutes(minute),
|
||||
ContentType = ContentType.TEXT,
|
||||
Content = new ContentText { Text = "Please have a look.", FileAttachments = [..attachments] },
|
||||
Role = ChatRole.USER,
|
||||
HideFromUser = hidden,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user