mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-12 05:21:36 +00:00
Enabled sending document analysis sessions with attachments in the chat (#627)
Some checks are pending
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Some checks are pending
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Co-authored-by: Thorsten Sommer <mail@tsommer.org>
This commit is contained in:
parent
3abc2bf0f1
commit
85af3ed651
@ -118,10 +118,47 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
|||||||
|
|
||||||
protected override bool SubmitDisabled => (this.IsNoPolicySelected || this.loadedDocumentPaths.Count==0);
|
protected override bool SubmitDisabled => (this.IsNoPolicySelected || this.loadedDocumentPaths.Count==0);
|
||||||
|
|
||||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
protected override ChatThread ConvertToChatThread
|
||||||
{
|
{
|
||||||
SystemPrompt = SystemPrompts.DEFAULT,
|
get
|
||||||
};
|
{
|
||||||
|
if (this.chatThread is null || this.chatThread.Blocks.Count < 2)
|
||||||
|
{
|
||||||
|
return new ChatThread
|
||||||
|
{
|
||||||
|
SystemPrompt = SystemPrompts.DEFAULT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ChatThread
|
||||||
|
{
|
||||||
|
ChatId = Guid.NewGuid(),
|
||||||
|
Name = string.Format(T("{0} - Document Analysis Session"), this.selectedPolicy?.PolicyName ?? T("Empty")),
|
||||||
|
SystemPrompt = SystemPrompts.DEFAULT,
|
||||||
|
Blocks =
|
||||||
|
[
|
||||||
|
// Replace the first "user block" (here, it was/is the block generated by the assistant) with a new one
|
||||||
|
// that includes the loaded document paths and a standard message about the previous analysis session:
|
||||||
|
new ContentBlock
|
||||||
|
{
|
||||||
|
Time = this.chatThread.Blocks.First().Time,
|
||||||
|
Role = ChatRole.USER,
|
||||||
|
HideFromUser = false,
|
||||||
|
ContentType = ContentType.TEXT,
|
||||||
|
Content = new ContentText
|
||||||
|
{
|
||||||
|
Text = this.T("The result of your previous document analysis session."),
|
||||||
|
FileAttachments = this.loadedDocumentPaths.ToList(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Then, append the last block of the current chat thread
|
||||||
|
// (which is expected to be the AI response):
|
||||||
|
this.chatThread.Blocks.Last(),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ResetForm()
|
protected override void ResetForm()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -382,6 +382,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
|||||||
-- None
|
-- None
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
||||||
|
|
||||||
|
-- {0} - Document Analysis Session
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} - Document Analysis Session"
|
||||||
|
|
||||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
||||||
|
|
||||||
@ -436,6 +439,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Export policy as configuration section
|
-- Export policy as configuration section
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
||||||
|
|
||||||
|
-- The result of your previous document analysis session.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "The result of your previous document analysis session."
|
||||||
|
|
||||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
||||||
|
|
||||||
@ -469,6 +475,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Document Analysis Assistant
|
-- Document Analysis Assistant
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
||||||
|
|
||||||
|
-- Empty
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Empty"
|
||||||
|
|
||||||
-- Analysis and output rules
|
-- Analysis and output rules
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
// Use chat thread sent by the user:
|
// Use chat thread sent by the user:
|
||||||
this.ChatThread = deferredContent;
|
this.ChatThread = deferredContent;
|
||||||
this.Logger.LogInformation($"The chat '{this.ChatThread.Name}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
this.Logger.LogInformation($"The chat '{this.ChatThread.ChatId}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||||
|
|
||||||
// We know already that the chat thread is not null,
|
// We know already that the chat thread is not null,
|
||||||
@ -202,7 +202,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
// Select the correct provider:
|
// Select the correct provider:
|
||||||
await this.SelectProviderWhenLoadingChat();
|
await this.SelectProviderWhenLoadingChat();
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -384,6 +384,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
|||||||
-- None
|
-- None
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "Keine"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "Keine"
|
||||||
|
|
||||||
|
-- {0} - Document Analysis Session
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} – Sitzung zur Dokumentenanalyse"
|
||||||
|
|
||||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Verwenden Sie die Analyse- und Ausgaberegeln, um festzulegen, wie die KI Ihre Dokumente bewertet und die Ergebnisse formatiert."
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Verwenden Sie die Analyse- und Ausgaberegeln, um festzulegen, wie die KI Ihre Dokumente bewertet und die Ergebnisse formatiert."
|
||||||
|
|
||||||
@ -438,6 +441,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Export policy as configuration section
|
-- Export policy as configuration section
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Exportieren Sie das Regelwerk als Konfigurationsabschnitt"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Exportieren Sie das Regelwerk als Konfigurationsabschnitt"
|
||||||
|
|
||||||
|
-- The result of your previous document analysis session.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "Das Ergebnis Ihrer vorherigen Dokumentenanalyse-Sitzung."
|
||||||
|
|
||||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Möchten Sie das Regelwerk '{0}' wirklich löschen?"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Möchten Sie das Regelwerk '{0}' wirklich löschen?"
|
||||||
|
|
||||||
@ -471,6 +477,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Document Analysis Assistant
|
-- Document Analysis Assistant
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Assistent für die Dokumentenanalyse"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Assistent für die Dokumentenanalyse"
|
||||||
|
|
||||||
|
-- Empty
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Leer"
|
||||||
|
|
||||||
-- Analysis and output rules
|
-- Analysis and output rules
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analyse- und Ausgaberegeln"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analyse- und Ausgaberegeln"
|
||||||
|
|
||||||
|
|||||||
@ -384,6 +384,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
|||||||
-- None
|
-- None
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
||||||
|
|
||||||
|
-- {0} - Document Analysis Session
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} - Document Analysis Session"
|
||||||
|
|
||||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
||||||
|
|
||||||
@ -438,6 +441,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Export policy as configuration section
|
-- Export policy as configuration section
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
||||||
|
|
||||||
|
-- The result of your previous document analysis session.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "The result of your previous document analysis session."
|
||||||
|
|
||||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
||||||
|
|
||||||
@ -471,6 +477,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
|||||||
-- Document Analysis Assistant
|
-- Document Analysis Assistant
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
||||||
|
|
||||||
|
-- Empty
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Empty"
|
||||||
|
|
||||||
-- Analysis and output rules
|
-- Analysis and output rules
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -11,8 +12,50 @@ namespace AIStudio.Tools;
|
|||||||
|
|
||||||
public static class WorkspaceBehaviour
|
public static class WorkspaceBehaviour
|
||||||
{
|
{
|
||||||
|
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(WorkspaceBehaviour));
|
||||||
|
|
||||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(WorkspaceBehaviour).Namespace, nameof(WorkspaceBehaviour));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Semaphores for synchronizing chat storage operations per chat.
|
||||||
|
/// This prevents race conditions when multiple threads try to write
|
||||||
|
/// the same chat file simultaneously.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<string, SemaphoreSlim> CHAT_STORAGE_SEMAPHORES = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timeout for acquiring the chat storage semaphore.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly TimeSpan SEMAPHORE_TIMEOUT = TimeSpan.FromSeconds(6);
|
||||||
|
|
||||||
|
private static SemaphoreSlim GetChatSemaphore(Guid workspaceId, Guid chatId)
|
||||||
|
{
|
||||||
|
var key = $"{workspaceId}_{chatId}";
|
||||||
|
return CHAT_STORAGE_SEMAPHORES.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to acquire the chat storage semaphore within the configured timeout.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="workspaceId">The workspace ID.</param>
|
||||||
|
/// <param name="chatId">The chat ID.</param>
|
||||||
|
/// <param name="callerName">The name of the calling method for logging purposes.</param>
|
||||||
|
/// <returns>A tuple containing whether the semaphore was acquired and the semaphore instance.</returns>
|
||||||
|
private static async Task<(bool Acquired, SemaphoreSlim Semaphore)> TryAcquireChatSemaphoreAsync(Guid workspaceId, Guid chatId, string callerName)
|
||||||
|
{
|
||||||
|
var semaphore = GetChatSemaphore(workspaceId, chatId);
|
||||||
|
var acquired = await semaphore.WaitAsync(SEMAPHORE_TIMEOUT);
|
||||||
|
|
||||||
|
if (!acquired)
|
||||||
|
LOG.LogWarning("Failed to acquire chat storage semaphore within {Timeout} seconds for workspace '{WorkspaceId}', chat '{ChatId}' in method '{CallerName}'. Skipping operation to prevent potential race conditions or deadlocks.",
|
||||||
|
SEMAPHORE_TIMEOUT.TotalSeconds,
|
||||||
|
workspaceId,
|
||||||
|
chatId,
|
||||||
|
callerName);
|
||||||
|
|
||||||
|
return (acquired, semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
public static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
@ -37,35 +80,52 @@ public static class WorkspaceBehaviour
|
|||||||
|
|
||||||
public static async Task StoreChat(ChatThread chat)
|
public static async Task StoreChat(ChatThread chat)
|
||||||
{
|
{
|
||||||
string chatDirectory;
|
// Try to acquire the semaphore for this specific chat to prevent concurrent writes to the same file:
|
||||||
if (chat.WorkspaceId == Guid.Empty)
|
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(chat.WorkspaceId, chat.ChatId, nameof(StoreChat));
|
||||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
if (!acquired)
|
||||||
else
|
return;
|
||||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
|
||||||
|
|
||||||
// Ensure the directory exists:
|
try
|
||||||
Directory.CreateDirectory(chatDirectory);
|
{
|
||||||
|
string chatDirectory;
|
||||||
|
if (chat.WorkspaceId == Guid.Empty)
|
||||||
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "tempChats", chat.ChatId.ToString());
|
||||||
|
else
|
||||||
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||||
|
|
||||||
// Save the chat name:
|
// Ensure the directory exists:
|
||||||
var chatNamePath = Path.Join(chatDirectory, "name");
|
Directory.CreateDirectory(chatDirectory);
|
||||||
await File.WriteAllTextAsync(chatNamePath, chat.Name);
|
|
||||||
|
|
||||||
// Save the thread as thread.json:
|
// Save the chat name:
|
||||||
var chatPath = Path.Join(chatDirectory, "thread.json");
|
var chatNamePath = Path.Join(chatDirectory, "name");
|
||||||
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
await File.WriteAllTextAsync(chatNamePath, chat.Name);
|
||||||
|
|
||||||
|
// Save the thread as thread.json:
|
||||||
|
var chatPath = Path.Join(chatDirectory, "thread.json");
|
||||||
|
await File.WriteAllTextAsync(chatPath, JsonSerializer.Serialize(chat, JSON_OPTIONS), Encoding.UTF8);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ChatThread?> LoadChat(LoadChat loadChat)
|
public static async Task<ChatThread?> LoadChat(LoadChat loadChat)
|
||||||
{
|
{
|
||||||
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
// Try to acquire the semaphore for this specific chat to prevent concurrent read/writes to the same file:
|
||||||
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(loadChat.WorkspaceId, loadChat.ChatId, nameof(LoadChat));
|
||||||
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
if (!acquired)
|
||||||
|
|
||||||
if(!Directory.Exists(chatPath))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var chatPath = loadChat.WorkspaceId == Guid.Empty
|
||||||
|
? Path.Join(SettingsManager.DataDirectory, "tempChats", loadChat.ChatId.ToString())
|
||||||
|
: Path.Join(SettingsManager.DataDirectory, "workspaces", loadChat.WorkspaceId.ToString(), loadChat.ChatId.ToString());
|
||||||
|
|
||||||
|
if(!Directory.Exists(chatPath))
|
||||||
|
return null;
|
||||||
|
|
||||||
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
var chatData = await File.ReadAllTextAsync(Path.Join(chatPath, "thread.json"), Encoding.UTF8);
|
||||||
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
var chat = JsonSerializer.Deserialize<ChatThread>(chatData, JSON_OPTIONS);
|
||||||
return chat;
|
return chat;
|
||||||
@ -74,6 +134,10 @@ public static class WorkspaceBehaviour
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> LoadWorkspaceName(Guid workspaceId)
|
public static async Task<string> LoadWorkspaceName(Guid workspaceId)
|
||||||
@ -144,7 +208,19 @@ public static class WorkspaceBehaviour
|
|||||||
else
|
else
|
||||||
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
chatDirectory = Path.Join(SettingsManager.DataDirectory, "workspaces", chat.WorkspaceId.ToString(), chat.ChatId.ToString());
|
||||||
|
|
||||||
Directory.Delete(chatDirectory, true);
|
// Try to acquire the semaphore to prevent deleting while another thread is writing:
|
||||||
|
var (acquired, semaphore) = await TryAcquireChatSemaphoreAsync(workspaceId, chatId, nameof(DeleteChat));
|
||||||
|
if (!acquired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(chatDirectory, true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task EnsureWorkspace(Guid workspaceId, string workspaceName)
|
private static async Task EnsureWorkspace(Guid workspaceId, string workspaceName)
|
||||||
|
|||||||
@ -3,8 +3,10 @@
|
|||||||
- Improved error handling for model loading in provider dialogs (LLMs, embeddings, transcriptions).
|
- Improved error handling for model loading in provider dialogs (LLMs, embeddings, transcriptions).
|
||||||
- Improved the microphone handling (transcription preview) so that all sound effects and the voice recording are processed without interruption.
|
- Improved the microphone handling (transcription preview) so that all sound effects and the voice recording are processed without interruption.
|
||||||
- Improved the handling of self-hosted providers in the configuration dialogs (LLMs, embeddings, and transcriptions) when the host cannot provide a list of models.
|
- Improved the handling of self-hosted providers in the configuration dialogs (LLMs, embeddings, and transcriptions) when the host cannot provide a list of models.
|
||||||
|
- Improved the document analysis assistant (in preview) by allowing users to send results to a new chat to ask follow-up questions. Thanks to Sabrina `Sabrina-devops` for this contribution.
|
||||||
- Fixed a logging bug that prevented log events from being recorded in some cases.
|
- Fixed a logging bug that prevented log events from being recorded in some cases.
|
||||||
- Fixed a bug that allowed adding a provider (LLM, embedding, or transcription) without selecting a model.
|
- Fixed a bug that allowed adding a provider (LLM, embedding, or transcription) without selecting a model.
|
||||||
- Fixed a bug with local transcription providers by handling errors correctly when the local provider is unavailable.
|
- Fixed a bug with local transcription providers by handling errors correctly when the local provider is unavailable.
|
||||||
- Fixed a bug with local transcription providers by correctly handling empty model IDs.
|
- Fixed a bug with local transcription providers by correctly handling empty model IDs.
|
||||||
- Fixed a bug affecting the transcription preview: previously, when you stopped music or other media, recorded or dictated text, and then tried to resume playback, the media wouldn’t resume as expected. This behavior is now fixed.
|
- Fixed a bug affecting the transcription preview: previously, when you stopped music or other media, recorded or dictated text, and then tried to resume playback, the media wouldn’t resume as expected. This behavior is now fixed.
|
||||||
|
- Fixed a rare bug that occurred when multiple threads tried to manage the same chat thread.
|
||||||
Loading…
Reference in New Issue
Block a user