Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions app/MindWork AI Studio/Assistants/AssistantBase.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,22 +328,40 @@ protected async Task<string> AddAIResponseAsync(DateTimeOffset time, bool hideCo
this.isProcessing = true;
this.StateHasChanged();

// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
// content to be streamed.
this.ChatThread = await aiText.CreateFromProviderAsync(this.ProviderSettings.CreateProvider(), this.ProviderSettings.Model, this.LastUserPrompt, this.ChatThread, this.CancellationTokenSource!.Token);

this.isProcessing = false;
this.StateHasChanged();

if(manageCancellationLocally)
try
{
this.CancellationTokenSource.Dispose();
this.CancellationTokenSource = null;
// Use the selected provider to get the AI response.
// By awaiting this line, we wait for the entire
// content to be streamed.
this.ChatThread = await aiText.CreateFromProviderAsync(this.ProviderSettings.CreateProvider(), this.ProviderSettings.Model, this.LastUserPrompt, this.ChatThread, this.CancellationTokenSource!.Token);

// Return the AI response:
return aiText.Text;
}
catch (ProviderRequestException e)
{
this.Logger.LogError(e, "The provider request failed for assistant '{AssistantTitle}'. Status={StatusCode}, Reason='{ReasonPhrase}', Body='{ResponseBody}'", this.Title, e.StatusCode, e.ReasonPhrase, e.ResponseBody);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, e.UserMessage));

if (this.resultingContentBlock is not null && string.IsNullOrWhiteSpace(aiText.Text))
{
this.ChatThread?.Blocks.Remove(this.resultingContentBlock);
this.resultingContentBlock = null;
}

return string.Empty;
}
finally
{
this.isProcessing = false;
this.StateHasChanged();

// Return the AI response:
return aiText.Text;
if(manageCancellationLocally)
{
this.CancellationTokenSource?.Dispose();
this.CancellationTokenSource = null;
}
}
}

private async Task CancelStreaming()
Expand Down
18 changes: 18 additions & 0 deletions app/MindWork AI Studio/Assistants/I18N/allTexts.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6469,6 +6469,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions"
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"

-- The provider '{0}' reported an error while streaming the response.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1008706234"] = "The provider '{0}' reported an error while streaming the response."

-- The provider rejected the request because too many requests were sent. Please wait a moment and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1028424693"] = "The provider rejected the request because too many requests were sent. Please wait a moment and try again."

-- The request to the LLM provider '{0}' (type={1}) timed out after {2} while {3}. Please try again or check whether the provider is still responding.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1069211263"] = "The request to the LLM provider '{0}' (type={1}) timed out after {2} while {3}. Please try again or check whether the provider is still responding."

Expand Down Expand Up @@ -6502,6 +6508,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "We tried to
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'"

-- The provider '{0}' reported an error: {1}
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T700894460"] = "The provider '{0}' reported an error: {1}"

-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe."

Expand Down Expand Up @@ -6562,6 +6571,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected"
-- We could not load models from '{0}'. The account or API key does not have the required permissions.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T1143085203"] = "We could not load models from '{0}'. The account or API key does not have the required permissions."

-- We could not load models from '{0}' because too many requests were sent. Please wait a moment and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T155481725"] = "We could not load models from '{0}' because too many requests were sent. Please wait a moment and try again."

-- We could not load models from '{0}'. The API key is probably missing, invalid, or expired.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T2041046579"] = "We could not load models from '{0}'. The API key is probably missing, invalid, or expired."

Expand All @@ -6571,9 +6583,15 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T21156887
-- We could not load models from '{0}' because the provider returned an unexpected response.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T2186844789"] = "We could not load models from '{0}' because the provider returned an unexpected response."

-- We could not load models from '{0}' because the account appears to have no API credits left.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T373339048"] = "We could not load models from '{0}' because the account appears to have no API credits left."

-- We could not load models from '{0}' due to an unknown error.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T3907712809"] = "We could not load models from '{0}' due to an unknown error."

-- It looks like you do not have any API credits left with OpenAI. Please add credits to your account and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T757371511"] = "It looks like you do not have any API credits left with OpenAI. Please add credits to your account and try again."

-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"

Expand Down
105 changes: 58 additions & 47 deletions app/MindWork AI Studio/Chat/ContentText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,59 +93,70 @@ public async Task<ChatThread> CreateFromProviderAsync(IProvider provider, Model

// Start another thread by using a task to uncouple
// the UI thread from the AI processing:
await Task.Run(async () =>
try
{
// We show the waiting animation until we get the first response:
this.InitialRemoteWait = true;

// Iterate over the responses from the AI:
await foreach (var contentStreamChunk in provider.StreamChatCompletion(chatModel, chatThread, settings, token))
await Task.Run(async () =>
{
// When the user cancels the request, we stop the loop:
if (token.IsCancellationRequested)
break;

// Stop the waiting animation:
this.InitialRemoteWait = false;
this.IsStreaming = true;

// Add the response to the text:
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:
var now = DateTimeOffset.Now;
switch (settings.ConfigurationData.App.IsSavingEnergy)
try
{
// Energy saving mode is off. We notify the UI
// as fast as possible -- no matter the odds:
case false:
await this.StreamingEvent();
break;

// Energy saving mode is on. We notify the UI
// only when the time between two events is
// greater than the minimum time:
case true when now - last > MIN_TIME:
last = now;
await this.StreamingEvent();
break;
}
}
// We show the waiting animation until we get the first response:
this.InitialRemoteWait = true;

// Stop the waiting animation (in case the loop
// was stopped, or no content was received):
this.InitialRemoteWait = false;
this.IsStreaming = false;
}, token);
// Iterate over the responses from the AI:
await foreach (var contentStreamChunk in provider.StreamChatCompletion(chatModel, chatThread, settings, token))
{
// When the user cancels the request, we stop the loop:
if (token.IsCancellationRequested)
break;

// Stop the waiting animation:
this.InitialRemoteWait = false;
this.IsStreaming = true;

// Add the response to the text:
this.Text += contentStreamChunk;

this.Text = this.Text.RemoveThinkTags().Trim();
// Merge the sources:
this.Sources.MergeSources(contentStreamChunk.Sources);

// Notify the UI that the content has changed,
// depending on the energy saving mode:
var now = DateTimeOffset.Now;
switch (settings.ConfigurationData.App.IsSavingEnergy)
{
// Energy saving mode is off. We notify the UI
// as fast as possible -- no matter the odds:
case false:
await this.StreamingEvent();
break;

// Energy saving mode is on. We notify the UI
// only when the time between two events is
// greater than the minimum time:
case true when now - last > MIN_TIME:
last = now;
await this.StreamingEvent();
break;
}
}
}
finally
{
// Stop the waiting animation (in case the loop
// was stopped, or no content was received):
this.InitialRemoteWait = false;
this.IsStreaming = false;
}
}, token);
}
finally
{
this.Text = this.Text.RemoveThinkTags().Trim();

// Inform the UI that the streaming is done:
await this.StreamingDone();
// Inform the UI that the streaming is done:
await this.StreamingDone();
}

return chatThread;
}

Expand Down
5 changes: 4 additions & 1 deletion app/MindWork AI Studio/Components/VoiceRecorder.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,10 @@ private async Task TranscribeRecordingAsync()
if (!transcriptionResult.Success)
{
this.Logger.LogWarning("The transcription request failed.");
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("Unfortunately, there was an error communicating with the AI system.")));
var userMessage = string.IsNullOrWhiteSpace(transcriptionResult.ErrorMessage)
? this.T("Unfortunately, there was an error communicating with the AI system.")
: transcriptionResult.ErrorMessage;
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, userMessage));
return;
}

Expand Down
33 changes: 25 additions & 8 deletions app/MindWork AI Studio/Pages/Writer.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace AIStudio.Pages;

public partial class Writer : MSGComponentBase
{
private static readonly ILogger<Writer> LOGGER = Program.LOGGER_FACTORY.CreateLogger<Writer>();
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
private readonly Timer typeTimer = new(TimeSpan.FromMilliseconds(1_500));

Expand Down Expand Up @@ -106,22 +107,38 @@ You are an assistant who helps with writing documents. You receive a sample
InitialRemoteWait = true,
};

this.chatThread?.Blocks.Add(new ContentBlock
var aiBlock = new ContentBlock
{
Time = time,
ContentType = ContentType.TEXT,
Role = ChatRole.AI,
Content = aiText,
});
};

this.chatThread?.Blocks.Add(aiBlock);

this.isStreaming = true;
this.StateHasChanged();

this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.providerSettings.Model, lastUserPrompt, this.chatThread);
this.suggestion = aiText.Text;

this.isStreaming = false;
this.StateHasChanged();

try
{
this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.providerSettings.Model, lastUserPrompt, this.chatThread);
this.suggestion = aiText.Text;
}
catch (ProviderRequestException e)
{
LOGGER.LogError(e, "The provider request failed for writer suggestions. Status={StatusCode}, Reason='{ReasonPhrase}', Body='{ResponseBody}'", e.StatusCode, e.ReasonPhrase, e.ResponseBody);
await this.MessageBus.SendError(new(Icons.Material.Filled.CloudOff, e.UserMessage));
this.suggestion = string.Empty;

if (string.IsNullOrWhiteSpace(aiText.Text))
this.chatThread?.Blocks.Remove(aiBlock);
}
finally
{
this.isStreaming = false;
this.StateHasChanged();
}
}

private void AcceptEntireSuggestion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6471,6 +6471,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Ihre Regieanweisungen"
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Der Server ist möglicherweise nicht erreichbar oder hat Probleme. Die Nachricht des Anbieters lautet: „{2}“"

-- The provider '{0}' reported an error while streaming the response.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1008706234"] = "Der Anbieter „{0}“ hat einen Fehler beim Streamen der Antwort gemeldet."

-- The provider rejected the request because too many requests were sent. Please wait a moment and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1028424693"] = "Der Anbieter hat die Anfrage abgelehnt, weil zu viele Anfragen gesendet wurden. Bitte warten Sie einen Moment und versuchen Sie es erneut."

-- The request to the LLM provider '{0}' (type={1}) timed out after {2} while {3}. Please try again or check whether the provider is still responding.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1069211263"] = "Die Anfrage an den LLM-Anbieter „{0}“ (Typ={1}) hat nach {2} während „{3}“ das Zeitlimit überschritten. Bitte versuchen Sie es erneut oder prüfen Sie, ob der Anbieter noch antwortet."

Expand Down Expand Up @@ -6504,6 +6510,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "Wir haben ve
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Die Daten des Chats, einschließlich aller Dateianhänge, sind vermutlich zu groß für das ausgewählte Modell und den Anbieter. Die Nachricht des Anbieters lautet: „{2}“"

-- The provider '{0}' reported an error: {1}
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T700894460"] = "Der Anbieter „{0}“ hat einen Fehler gemeldet: {1}"

-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "Das Vertrauensniveau dieses Anbieters wurde **noch nicht** gründlich **untersucht und bewertet**. Wir wissen nicht, ob ihre Daten sicher sind."

Expand Down Expand Up @@ -6564,6 +6573,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "Kein Modell ausgew
-- We could not load models from '{0}'. The account or API key does not have the required permissions.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T1143085203"] = "Wir konnten keine Modelle von '{0}' laden. Das Konto oder der API-Schlüssel verfügt nicht über die erforderlichen Berechtigungen."

-- We could not load models from '{0}' because too many requests were sent. Please wait a moment and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T155481725"] = "Wir konnten keine Modelle von „{0}“ laden, da zu viele Anfragen gesendet wurden. Bitte warten Sie einen Moment und versuchen Sie es erneut."

-- We could not load models from '{0}'. The API key is probably missing, invalid, or expired.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T2041046579"] = "Modelle aus '{0}' konnten nicht geladen werden. Wahrscheinlich fehlt der API-Schlüssel, ist ungültig oder abgelaufen."

Expand All @@ -6573,9 +6585,15 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T21156887
-- We could not load models from '{0}' because the provider returned an unexpected response.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T2186844789"] = "Wir konnten keine Modelle von '{0}' laden, da der Anbieter eine unerwartete Antwort zurückgegeben hat."

-- We could not load models from '{0}' because the account appears to have no API credits left.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T373339048"] = "Modelle konnten nicht von „{0}“ geladen werden, da das Konto offenbar keine API-Guthaben mehr hat."

-- We could not load models from '{0}' due to an unknown error.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODELLOADFAILUREREASONEXTENSIONS::T3907712809"] = "Wir konnten die Modelle aus '{0}' aufgrund eines unbekannten Fehlers nicht laden."

-- It looks like you do not have any API credits left with OpenAI. Please add credits to your account and try again.
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::OPENAI::PROVIDEROPENAI::T757371511"] = "Anscheinend haben Sie bei OpenAI kein API-Guthaben mehr. Bitte fügen Sie Ihrem Konto Guthaben hinzu und versuchen Sie es erneut."

-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Modell wie in whisper.cpp konfiguriert"

Expand Down
Loading