mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 00:53:37 +00:00
Personal Code Review
This commit is contained in:
parent
601fe727ba
commit
34774531fd
@ -5290,6 +5290,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "The transc
|
||||
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
|
||||
|
||||
-- Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1047391993"] = "Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with."
|
||||
|
||||
-- Move chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat"
|
||||
|
||||
@ -5386,6 +5389,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat"
|
||||
-- Add Workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace"
|
||||
|
||||
-- Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3699436634"] = "Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost."
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Chat Name"
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@
|
||||
@if (!this.IsLastContentBlock && this.Role is ChatRole.AI && this.RollbackFunc is not null)
|
||||
{
|
||||
<MudTooltip Text="@T("Roll back to this response")" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Restore" Color="Color.Default" Disabled="@(!this.RollbackEnabled)" OnClick="@this.RollbackBlock"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Restore" Color="Color.Default" Disabled="@(!this.RollbackEnabled())" OnClick="@this.RollbackBlock"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (this.RemoveBlockFunc is not null)
|
||||
|
||||
@ -90,7 +90,7 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
public Func<bool> RegenerateEnabled { get; set; } = () => false;
|
||||
|
||||
[Parameter]
|
||||
public bool RollbackEnabled { get; set; }
|
||||
public Func<bool> RollbackEnabled { get; set; } = () => false;
|
||||
|
||||
/// <summary>
|
||||
/// What the export offers, used both as the label of the export button and as the title of
|
||||
@ -789,7 +789,7 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
|
||||
private async Task RollbackBlock()
|
||||
{
|
||||
if (this.RollbackFunc is null || this.Role is not ChatRole.AI || !this.RollbackEnabled)
|
||||
if (this.RollbackFunc is null || this.Role is not ChatRole.AI || !this.RollbackEnabled())
|
||||
return;
|
||||
|
||||
var rollback = await this.DialogService.ShowMessageBox(
|
||||
@ -873,4 +873,4 @@ public partial class ContentBlockComponent : MSGComponentBase
|
||||
|
||||
await this.DisposeMathContainerIfNeededAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,7 @@
|
||||
RegenerateFunc="@this.RegenerateBlock"
|
||||
RegenerateEnabled="@(() => this.IsProviderSelected && this.ChatThread.IsLLMProviderAllowed(this.Provider))"
|
||||
RollbackFunc="@this.RollbackBlock"
|
||||
RollbackEnabled="@(!this.IsCurrentChatStreaming)"
|
||||
RollbackEnabled="@(() => !this.IsCurrentChatStreaming)"
|
||||
EditLastBlockFunc="@this.EditLastBlock"
|
||||
EditLastUserBlockFunc="@this.EditLastUserBlock"/>
|
||||
}
|
||||
|
||||
@ -1403,6 +1403,16 @@ public partial class ChatComponent : MSGComponentBase
|
||||
if (!this.ChatThread.RemoveBlocksAfter(aiBlock))
|
||||
return;
|
||||
|
||||
//
|
||||
// Only the working state of the next turn is reset here: the augmented data and the
|
||||
// AI-selected data sources are rebuilt by the RAG process anyway, so carrying the ones of a
|
||||
// removed message over would just put stale context into the system prompt.
|
||||
//
|
||||
// What stays is everything the thread ratchets for security reasons, namely DataSecurity and
|
||||
// RequiredProviderConfidence. Both only ever tighten, because the data which raised them was
|
||||
// seen by this thread. Removing the message that brought it in does not unsee it, so the
|
||||
// chat keeps demanding the same of every provider which continues it.
|
||||
//
|
||||
this.ChatThread.AugmentedData = string.Empty;
|
||||
this.ChatThread.AISelectedDataSources = [];
|
||||
this.dataSourceSelectionComponent?.ChangeOptionWithoutSaving(this.ChatThread.DataSourceOptions, this.ChatThread.AISelectedDataSources);
|
||||
|
||||
@ -152,4 +152,4 @@ else
|
||||
}
|
||||
</ItemTemplate>
|
||||
</MudTreeView>
|
||||
}
|
||||
}
|
||||
@ -791,11 +791,25 @@ public partial class Workspaces : MSGComponentBase
|
||||
if (this.AIJobService.IsChatGenerationActive(chat.ChatId) || this.MediaTranscriptionService.IsBusy(mediaOwner))
|
||||
return;
|
||||
|
||||
//
|
||||
// Copying the chat which is open right now takes its in-memory state, so whatever the user
|
||||
// has not saved yet ends up in the copy while the original keeps the state it was stored
|
||||
// with. Copying any other chat replaces the open one, so its unsaved changes are gone.
|
||||
// Both outcomes are surprising enough to deserve their own wording.
|
||||
//
|
||||
var openChat = this.CurrentChatThread;
|
||||
var isCopyOfOpenChat = openChat is not null && openChat.ChatId == chat.ChatId;
|
||||
if (await MessageBus.INSTANCE.SendMessageUseFirstResult<bool, bool>(this, Event.HAS_CHAT_UNSAVED_CHANGES))
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, T("Are you sure you want to load another chat? All unsaved changes will be lost.") },
|
||||
{
|
||||
x => x.Message, isCopyOfOpenChat switch
|
||||
{
|
||||
true => T("Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with."),
|
||||
false => T("Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost."),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Copy Chat"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
@ -804,7 +818,7 @@ public partial class Workspaces : MSGComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceChat = this.CurrentChatThread is { } currentChat && currentChat.ChatId == chat.ChatId ? currentChat : chat;
|
||||
var sourceChat = isCopyOfOpenChat ? openChat! : chat;
|
||||
var copy = await WorkspaceBehaviour.CopyChatAsync(sourceChat);
|
||||
|
||||
await this.LoadTreeItemsAsync(startPrefetch: false);
|
||||
|
||||
@ -3265,7 +3265,7 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347088452"] = "Ergebni
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Möchten Sie diese Nachricht wirklich löschen?"
|
||||
|
||||
-- Do you really want to roll back this chat to this AI response? All later messages and their attachments will be permanently removed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347725178"] = "Möchtest du diesen Chat wirklich auf diese KI-Antwort zurücksetzen? Alle späteren Nachrichten und deren Anhänge werden dauerhaft gelöscht."
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347725178"] = "Möchten Sie diesen Chat wirklich auf diese KI-Antwort zurücksetzen? Alle späteren Nachrichten und deren Anhänge werden dauerhaft gelöscht."
|
||||
|
||||
-- Yes, remove the AI response and edit it
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Ja, entferne die KI-Antwort und bearbeite sie."
|
||||
@ -5292,6 +5292,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "Das Ergebn
|
||||
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Möchten Sie den Chat „{0}“ im Arbeitsbereich „{1}“ wirklich löschen?"
|
||||
|
||||
-- Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1047391993"] = "Möchten Sie diesen Chat kopieren? Ihre ungespeicherten Änderungen wandern in die Kopie, und der ursprüngliche Chat behält den zuletzt gespeicherten Stand."
|
||||
|
||||
-- Move chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Chat verschieben"
|
||||
|
||||
@ -5388,6 +5391,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Chat laden"
|
||||
-- Add Workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Arbeitsbereich hinzufügen"
|
||||
|
||||
-- Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3699436634"] = "Möchten Sie diesen Chat kopieren? Die Kopie wird anschließend geöffnet, daher gehen alle ungespeicherten Änderungen des aktuell geöffneten Chats verloren."
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Name des Chat"
|
||||
|
||||
|
||||
@ -5292,6 +5292,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "The transc
|
||||
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
|
||||
|
||||
-- Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1047391993"] = "Do you want to copy this chat? Your unsaved changes move into the copy, and the original chat keeps the state it was last saved with."
|
||||
|
||||
-- Move chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat"
|
||||
|
||||
@ -5388,6 +5391,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat"
|
||||
-- Add Workspace
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace"
|
||||
|
||||
-- Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3699436634"] = "Do you want to copy this chat? The copy is opened afterwards, so all unsaved changes of the chat you have open right now will be lost."
|
||||
|
||||
-- Chat Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3891063690"] = "Chat Name"
|
||||
|
||||
|
||||
@ -752,11 +752,16 @@ public static class WorkspaceBehaviour
|
||||
return Directory.Exists(chatPath);
|
||||
}
|
||||
|
||||
public static async Task StoreChatAsync(ChatThread chat)
|
||||
/// <summary>
|
||||
/// Stores a chat, unless another operation holds its lock for longer than the semaphore timeout.
|
||||
/// </summary>
|
||||
/// <param name="chat">The chat to store.</param>
|
||||
/// <returns>True when the chat was written; false when the operation was skipped to avoid a race.</returns>
|
||||
public static async Task<bool> StoreChatAsync(ChatThread chat)
|
||||
{
|
||||
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(chat.WorkspaceId, chat.ChatId, nameof(StoreChatAsync));
|
||||
if (!acquired)
|
||||
return;
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
@ -775,6 +780,7 @@ public static class WorkspaceBehaviour
|
||||
|
||||
var lastEditTime = File.GetLastWriteTimeUtc(chatPath);
|
||||
await UpdateCacheAfterChatStored(chat.WorkspaceId, chat.ChatId, chatDirectory, chat.Name, lastEditTime);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -782,6 +788,15 @@ public static class WorkspaceBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a chat into a new chat of the same workspace, including its managed transcript files.
|
||||
/// </summary>
|
||||
/// <param name="sourceChat">The chat to copy. Its own files and state stay untouched.</param>
|
||||
/// <returns>The persisted copy.</returns>
|
||||
/// <remarks>
|
||||
/// The copy is written before it is returned, so the caller may open it right away. Runtime-only
|
||||
/// state of the source is not part of the copy: it is rebuilt when the copy gets loaded.
|
||||
/// </remarks>
|
||||
public static async Task<ChatThread> CopyChatAsync(ChatThread sourceChat)
|
||||
{
|
||||
var serializedChat = JsonSerializer.Serialize(sourceChat, JSON_OPTIONS);
|
||||
@ -797,7 +812,15 @@ public static class WorkspaceBehaviour
|
||||
try
|
||||
{
|
||||
CopyManagedTranscriptAttachments(copiedChat, targetDirectory);
|
||||
await StoreChatAsync(copiedChat);
|
||||
|
||||
//
|
||||
// Storing is skipped instead of failing when the chat lock cannot be taken. For a copy
|
||||
// that must not pass as success: the caller would open a chat which is not on disk,
|
||||
// while the transcript files copied above would stay behind as orphans.
|
||||
//
|
||||
if (!await StoreChatAsync(copiedChat))
|
||||
throw new IOException($"The copied chat could not be stored: '{targetDirectory}'.");
|
||||
|
||||
return copiedChat;
|
||||
}
|
||||
catch
|
||||
@ -835,7 +858,22 @@ public static class WorkspaceBehaviour
|
||||
string targetTranscriptDirectory,
|
||||
Dictionary<string, ManagedTranscriptAttachment> copiedPaths)
|
||||
{
|
||||
var sourcePath = Path.GetFullPath(source.FilePath);
|
||||
//
|
||||
// A thread which was edited outside the app may name a path which is not a path at all.
|
||||
// Such an attachment keeps pointing at whatever the source named: it is already broken in
|
||||
// the source chat, and letting it take the whole copy down would be worse.
|
||||
//
|
||||
string sourcePath;
|
||||
try
|
||||
{
|
||||
sourcePath = Path.GetFullPath(source.FilePath);
|
||||
}
|
||||
catch (Exception e) when (e is ArgumentException or NotSupportedException or PathTooLongException)
|
||||
{
|
||||
LOG.LogWarning(e, "Could not resolve the transcript path '{FilePath}' while copying chat '{ChatId}'. The attachment is kept as it is.", source.FilePath, chat.ChatId);
|
||||
return source;
|
||||
}
|
||||
|
||||
if (copiedPaths.TryGetValue(sourcePath, out var existingCopy))
|
||||
return existingCopy;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# v26.9.1, build 256 (2026-09-xx xx:xx UTC)
|
||||
- Added a way to copy an entire chat and continue the copy independently while the original conversation stays unchanged.
|
||||
- Added a way to roll a chat back to an earlier AI response. The response you choose stays, everything after it is permanently removed, and your current draft and attachments remain ready so you can continue from there.
|
||||
- Added a way to copy an entire chat. The copy opens right away so you can continue in it, while the original conversation stays exactly as it was.
|
||||
- Added a way to roll a chat back to an earlier AI response. The response you pick stays, and every message after it is removed permanently, together with the attachments of those messages.
|
||||
- Added tools that AI models can use on their own, starting with Web Search and Read Web Page. When you ask something a model cannot answer from what it knows, it now searches the web, reads the pages it found, and answers with the sources it used. You decide which tools a model may use, right below the message field, and you can watch it work: AI Studio shows which tool is running and, afterward, every call it made with its result. Whether tools are offered at all depends on the model because it has to support them. Read Web Page works right away; for Web Search you pick a search service in the app settings — Tavily or Staan with a free API key, or a SearXNG instance you run yourself. Set up more than one, and they can take turns when one of them finds nothing, or be asked all at once with their results combined. Many thanks to Peer Schütt (`peerschuett`) and Nils Kruthoff (`nilskruthoff`) for building this feature.
|
||||
- Added answers that appear word by word even while the AI uses its tools. You read along as the model writes, including the short note it puts down before it looks something up, and the answer that follows a tool call arrives the same way instead of all at once at the end.
|
||||
- Added safeguards around everything these tools bring back. Anything fetched from the web is treated as untrusted: AI Studio removes instructions hidden in a page before a model reads it and tells you when it did, exactly as it already does for the documents and web pages you load yourself. A model can never point a tool at your own network. Each tool states how much you have to trust a provider before it may be used with it, so your questions do not travel further than you allow. You can adjust that requirement per tool in the app settings.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user