mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-03-29 17:31:37 +00:00
Merge branch 'main' into PowerPoint
This commit is contained in:
commit
e40fad69e9
25
AGENTS.md
25
AGENTS.md
@ -144,7 +144,7 @@ Multi-level confidence scheme allows users to control which providers see which
|
|||||||
|
|
||||||
**Rust:**
|
**Rust:**
|
||||||
- Tauri 1.8 - Desktop application framework
|
- Tauri 1.8 - Desktop application framework
|
||||||
- Rocket 0.5 - HTTPS API server
|
- Rocket - HTTPS API server
|
||||||
- tokio - Async runtime
|
- tokio - Async runtime
|
||||||
- keyring - OS keyring integration
|
- keyring - OS keyring integration
|
||||||
- pdfium-render - PDF text extraction
|
- pdfium-render - PDF text extraction
|
||||||
@ -152,7 +152,7 @@ Multi-level confidence scheme allows users to control which providers see which
|
|||||||
|
|
||||||
**.NET:**
|
**.NET:**
|
||||||
- Blazor Server - UI framework
|
- Blazor Server - UI framework
|
||||||
- MudBlazor 8.12 - Component library
|
- MudBlazor - Component library
|
||||||
- LuaCSharp - Lua scripting engine
|
- LuaCSharp - Lua scripting engine
|
||||||
- HtmlAgilityPack - HTML parsing
|
- HtmlAgilityPack - HTML parsing
|
||||||
- ReverseMarkdown - HTML to Markdown conversion
|
- ReverseMarkdown - HTML to Markdown conversion
|
||||||
@ -168,7 +168,7 @@ Multi-level confidence scheme allows users to control which providers see which
|
|||||||
|
|
||||||
1. Create changelog file: `app/MindWork AI Studio/wwwroot/changelog/vX.Y.Z.md`
|
1. Create changelog file: `app/MindWork AI Studio/wwwroot/changelog/vX.Y.Z.md`
|
||||||
2. Commit changelog
|
2. Commit changelog
|
||||||
3. Run from `app/Build`: `dotnet run release --action <patch|minor|major>`
|
3. Run from `app/Build`: `dotnet run release --action <build|month|year>`
|
||||||
4. Create PR with version bump and changes
|
4. Create PR with version bump and changes
|
||||||
5. After PR merge, maintainer creates git tag: `vX.Y.Z`
|
5. After PR merge, maintainer creates git tag: `vX.Y.Z`
|
||||||
6. GitHub Actions builds release binaries for all platforms
|
6. GitHub Actions builds release binaries for all platforms
|
||||||
@ -183,3 +183,22 @@ Multi-level confidence scheme allows users to control which providers see which
|
|||||||
- **MudBlazor** - Component library requires DI setup in Program.cs
|
- **MudBlazor** - Component library requires DI setup in Program.cs
|
||||||
- **Encryption** - Initialized before Rust service is marked ready
|
- **Encryption** - Initialized before Rust service is marked ready
|
||||||
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
||||||
|
|
||||||
|
## Changelogs
|
||||||
|
Changelogs are located in `app/MindWork AI Studio/wwwroot/changelog/` with filenames `vX.Y.Z.md`. These changelogs are meant to be for normal end-users
|
||||||
|
and should be written in a non-technical way, focusing on user-facing changes and improvements. Additionally, changes made regarding the plugin system
|
||||||
|
should be included in the changelog, especially if they affect how users can configure the app or if they introduce new capabilities for plugins. Plugin
|
||||||
|
developers should also be informed about these changes, as they might need to update their plugins accordingly. When adding entries to the changelog,
|
||||||
|
please ensure they are clear and concise, avoiding technical jargon where possible. Each entry starts with a dash and a space (`- `) and one of the
|
||||||
|
following words:
|
||||||
|
|
||||||
|
- Added
|
||||||
|
- Improved
|
||||||
|
- Changed
|
||||||
|
- Fixed
|
||||||
|
- Updated
|
||||||
|
- Removed
|
||||||
|
- Downgraded
|
||||||
|
- Upgraded
|
||||||
|
|
||||||
|
The entire changelog is sorted by these categories in the order shown above. The language used for the changelog is US English.
|
||||||
@ -80,10 +80,10 @@
|
|||||||
|
|
||||||
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
|
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
|
||||||
{
|
{
|
||||||
@if (this.ShowSendTo)
|
@if (this.ShowSendTo && this.VisibleSendToAssistants.Count > 0)
|
||||||
{
|
{
|
||||||
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
@foreach (var assistant in this.VisibleSendToAssistants)
|
||||||
{
|
{
|
||||||
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, new()))">
|
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, new()))">
|
||||||
@assistant.Name()
|
@assistant.Name()
|
||||||
@ -112,14 +112,17 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SendToButton sendToButton:
|
case SendToButton sendToButton:
|
||||||
|
@if (this.VisibleSendToAssistants.Count > 0)
|
||||||
|
{
|
||||||
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
<MudMenu AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomLeft" StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="@TB("Send to ...")" Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
@foreach (var assistant in this.VisibleSendToAssistants)
|
||||||
{
|
{
|
||||||
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, sendToButton))">
|
<MudMenuItem OnClick="@(async () => await this.SendToAssistant(assistant, sendToButton))">
|
||||||
@assistant.Name()
|
@assistant.Name()
|
||||||
</MudMenuItem>
|
</MudMenuItem>
|
||||||
}
|
}
|
||||||
</MudMenu>
|
</MudMenu>
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,6 +150,9 @@
|
|||||||
{
|
{
|
||||||
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/>
|
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<MudSpacer />
|
||||||
|
<HalluzinationReminder ContainerClass="my-0 ml-2"/>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</FooterContent>
|
</FooterContent>
|
||||||
</InnerScrolling>
|
</InnerScrolling>
|
||||||
|
|||||||
@ -106,6 +106,13 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
{
|
{
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
|
|
||||||
|
if (!this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Title))
|
||||||
|
{
|
||||||
|
this.Logger.LogInformation("Assistant '{AssistantTitle}' is hidden. Redirecting to the assistants overview.", this.Title);
|
||||||
|
this.NavigationManager.NavigateTo(Routes.ASSISTANTS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.formChangeTimer.AutoReset = false;
|
this.formChangeTimer.AutoReset = false;
|
||||||
this.formChangeTimer.Elapsed += async (_, _) =>
|
this.formChangeTimer.Elapsed += async (_, _) =>
|
||||||
{
|
{
|
||||||
@ -143,6 +150,11 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
|
|
||||||
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
|
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
|
||||||
|
|
||||||
|
private IReadOnlyList<Tools.Components> VisibleSendToAssistants => Enum.GetValues<AIStudio.Tools.Components>()
|
||||||
|
.Where(this.CanSendToAssistant)
|
||||||
|
.OrderBy(component => component.Name().Length)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||||
{
|
{
|
||||||
if(provider.UsedLLMProvider == LLMProviders.NONE)
|
if(provider.UsedLLMProvider == LLMProviders.NONE)
|
||||||
@ -339,7 +351,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
|
|
||||||
protected Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
|
protected Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
|
||||||
{
|
{
|
||||||
if (!destination.AllowSendTo())
|
if (!this.CanSendToAssistant(destination))
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch
|
var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch
|
||||||
@ -370,6 +382,14 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanSendToAssistant(Tools.Components component)
|
||||||
|
{
|
||||||
|
if (!component.AllowSendTo())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.SettingsManager.IsAssistantVisible(component, withLogging: false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InnerResetForm()
|
private async Task InnerResetForm()
|
||||||
{
|
{
|
||||||
this.resultingContentBlock = null;
|
this.resultingContentBlock = null;
|
||||||
|
|||||||
@ -1660,6 +1660,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Move chat
|
|||||||
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost."
|
||||||
|
|
||||||
|
-- Bold
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1165397398"] = "Bold"
|
||||||
|
|
||||||
-- Stop generation
|
-- Stop generation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation"
|
||||||
|
|
||||||
@ -1672,9 +1675,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your
|
|||||||
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')"
|
||||||
|
|
||||||
|
-- Code
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||||
|
|
||||||
|
-- Italic
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Italic"
|
||||||
|
|
||||||
-- Profile usage is disabled according to your chat template settings.
|
-- Profile usage is disabled according to your chat template settings.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."
|
||||||
|
|
||||||
|
-- Bulleted List
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Bulleted List"
|
||||||
|
|
||||||
-- Delete this chat & start a new one.
|
-- Delete this chat & start a new one.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one."
|
||||||
|
|
||||||
@ -1693,6 +1705,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Start new
|
|||||||
-- Start temporary chat
|
-- Start temporary chat
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Start temporary chat"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Start temporary chat"
|
||||||
|
|
||||||
|
-- Heading
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||||
|
|
||||||
-- Please select the workspace where you want to move the chat to.
|
-- Please select the workspace where you want to move the chat to.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||||
|
|
||||||
@ -1837,6 +1852,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T700666808"] = "Mana
|
|||||||
-- Available Data Sources
|
-- Available Data Sources
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources"
|
||||||
|
|
||||||
|
-- LLMs can make mistakes. Check important information.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs can make mistakes. Check important information."
|
||||||
|
|
||||||
-- Issues
|
-- Issues
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
|
||||||
|
|
||||||
@ -3601,6 +3619,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Failed to l
|
|||||||
-- Please enter a model name.
|
-- Please enter a model name.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
|
||||||
|
|
||||||
|
-- Additional API parameters must form a JSON object.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Additional API parameters must form a JSON object."
|
||||||
|
|
||||||
-- Model
|
-- Model
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
|
||||||
|
|
||||||
@ -3622,12 +3643,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Na
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \\\"temperature\\\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
|
||||||
|
|
||||||
-- Show available models
|
-- Show available models
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||||
|
|
||||||
|
-- Duplicate key '{0}' found.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Duplicate key '{0}' found."
|
||||||
|
|
||||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
||||||
|
|
||||||
@ -5410,6 +5437,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to
|
|||||||
-- Motivation
|
-- Motivation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
||||||
|
|
||||||
|
-- not available
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3574465749"] = "not available"
|
||||||
|
|
||||||
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."
|
||||||
|
|
||||||
@ -5431,6 +5461,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3986423270"] = "Check Pandoc Ins
|
|||||||
-- Versions
|
-- Versions
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versions"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versions"
|
||||||
|
|
||||||
|
-- Database
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4036243672"] = "Database"
|
||||||
|
|
||||||
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system."
|
||||||
|
|
||||||
@ -6019,6 +6052,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T3893997203"] = "
|
|||||||
-- Trust all LLM providers
|
-- Trust all LLM providers
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
||||||
|
|
||||||
|
-- Reason
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T1093747001"] = "Reason"
|
||||||
|
|
||||||
|
-- Unavailable
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T3662391977"] = "Unavailable"
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T6222351"] = "Status"
|
||||||
|
|
||||||
-- Storage size
|
-- Storage size
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
||||||
|
|
||||||
|
|||||||
@ -84,6 +84,9 @@ public record FileAttachment(FileAttachmentType Type, string FileName, string Fi
|
|||||||
{
|
{
|
||||||
var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
|
var extension = Path.GetExtension(filePath).TrimStart('.').ToLowerInvariant();
|
||||||
|
|
||||||
|
if (FileTypeFilter.Executables.FilterExtensions.Contains(extension))
|
||||||
|
return FileAttachmentType.FORBIDDEN;
|
||||||
|
|
||||||
// Check if it's an image file:
|
// Check if it's an image file:
|
||||||
if (FileTypeFilter.AllImages.FilterExtensions.Contains(extension))
|
if (FileTypeFilter.AllImages.FilterExtensions.Contains(extension))
|
||||||
return FileAttachmentType.IMAGE;
|
return FileAttachmentType.IMAGE;
|
||||||
@ -96,7 +99,8 @@ public record FileAttachment(FileAttachmentType Type, string FileName, string Fi
|
|||||||
if (FileTypeFilter.PDF.FilterExtensions.Contains(extension) ||
|
if (FileTypeFilter.PDF.FilterExtensions.Contains(extension) ||
|
||||||
FileTypeFilter.Text.FilterExtensions.Contains(extension) ||
|
FileTypeFilter.Text.FilterExtensions.Contains(extension) ||
|
||||||
FileTypeFilter.AllOffice.FilterExtensions.Contains(extension) ||
|
FileTypeFilter.AllOffice.FilterExtensions.Contains(extension) ||
|
||||||
FileTypeFilter.AllSourceCode.FilterExtensions.Contains(extension))
|
FileTypeFilter.AllSourceCode.FilterExtensions.Contains(extension) ||
|
||||||
|
FileTypeFilter.IsAllowedSourceLikeFileName(filePath))
|
||||||
return FileAttachmentType.DOCUMENT;
|
return FileAttachmentType.DOCUMENT;
|
||||||
|
|
||||||
// All other file types are forbidden:
|
// All other file types are forbidden:
|
||||||
|
|||||||
@ -54,7 +54,8 @@
|
|||||||
Class="@this.UserInputClass"
|
Class="@this.UserInputClass"
|
||||||
Style="@this.UserInputStyle"/>
|
Style="@this.UserInputStyle"/>
|
||||||
</MudElement>
|
</MudElement>
|
||||||
<MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey;">
|
<MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey; gap: 2px;">
|
||||||
|
|
||||||
@if (
|
@if (
|
||||||
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
|
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
|
||||||
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
|
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
|
||||||
@ -82,8 +83,8 @@
|
|||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ChatTemplateSelection CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
|
<ChatTemplateSelection MarginLeft="" CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
|
||||||
<AttachDocuments Name="File Attachments" Layer="@DropLayers.PAGES" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/>
|
|
||||||
|
|
||||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||||
{
|
{
|
||||||
@ -99,6 +100,35 @@
|
|||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<AttachDocuments Name="File Attachments" Layer="@DropLayers.PAGES" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/>
|
||||||
|
|
||||||
|
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||||
|
|
||||||
|
<MudTooltip Text="@T("Bold")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.FormatBold" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BOLD)" Disabled="@this.IsInputForbidden()"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="@T("Italic")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.FormatItalic" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_ITALIC)" Disabled="@this.IsInputForbidden()"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="@T("Heading")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.TextFields" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_HEADING)" Disabled="@this.IsInputForbidden()"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="@T("Bulleted List")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.FormatListBulleted" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BULLET_LIST)" Disabled="@this.IsInputForbidden()"/>
|
||||||
|
</MudTooltip>
|
||||||
|
<MudTooltip Text="@T("Code")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Code" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_CODE)" Disabled="@this.IsInputForbidden()"/>
|
||||||
|
</MudTooltip>
|
||||||
|
|
||||||
|
<MudDivider Vertical="true" Style="height: 24px; align-self: center;"/>
|
||||||
|
|
||||||
|
<ProfileSelection MarginLeft="" CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" Disabled="@(!this.currentChatTemplate.AllowProfileUsage)" DisabledText="@T("Profile usage is disabled according to your chat template settings.")"/>
|
||||||
|
|
||||||
|
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||||
|
{
|
||||||
|
<DataSourceSelection @ref="@this.dataSourceSelectionComponent" PopoverTriggerMode="PopoverTriggerMode.BUTTON" LLMProvider="@this.Provider" DataSourceOptions="@this.GetCurrentDataSourceOptions()" DataSourceOptionsChanged="@(async options => await this.SetCurrentDataSourceOptions(options))" DataSourcesAISelected="@this.GetAgentSelectedDataSources()"/>
|
||||||
|
}
|
||||||
|
|
||||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||||
{
|
{
|
||||||
<ConfidenceInfo Mode="PopoverTriggerMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
<ConfidenceInfo Mode="PopoverTriggerMode.ICON" LLMProvider="@this.Provider.UsedLLMProvider"/>
|
||||||
@ -111,12 +141,6 @@
|
|||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ProfileSelection CurrentProfile="@this.currentProfile" CurrentProfileChanged="@this.ProfileWasChanged" Disabled="@(!this.currentChatTemplate.AllowProfileUsage)" DisabledText="@T("Profile usage is disabled according to your chat template settings.")"/>
|
|
||||||
|
|
||||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
|
||||||
{
|
|
||||||
<DataSourceSelection @ref="@this.dataSourceSelectionComponent" PopoverTriggerMode="PopoverTriggerMode.BUTTON" PopoverButtonClasses="ma-3" LLMProvider="@this.Provider" DataSourceOptions="@this.GetCurrentDataSourceOptions()" DataSourceOptionsChanged="@(async options => await this.SetCurrentDataSourceOptions(options))" DataSourcesAISelected="@this.GetAgentSelectedDataSources()"/>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!this.ChatThread.IsLLMProviderAllowed(this.Provider))
|
@if (!this.ChatThread.IsLLMProviderAllowed(this.Provider))
|
||||||
{
|
{
|
||||||
@ -124,7 +148,9 @@
|
|||||||
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Error"/>
|
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Error"/>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
}
|
}
|
||||||
<MudIconButton />
|
|
||||||
|
<MudSpacer />
|
||||||
|
<HalluzinationReminder ContainerClass="my-0 ml-2 me-2"/>
|
||||||
</MudToolBar>
|
</MudToolBar>
|
||||||
</FooterContent>
|
</FooterContent>
|
||||||
</InnerScrolling>
|
</InnerScrolling>
|
||||||
@ -13,6 +13,13 @@ namespace AIStudio.Components;
|
|||||||
|
|
||||||
public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||||
{
|
{
|
||||||
|
private const string CHAT_INPUT_ID = "chat-user-input";
|
||||||
|
private const string MARKDOWN_CODE = "code";
|
||||||
|
private const string MARKDOWN_BOLD = "bold";
|
||||||
|
private const string MARKDOWN_ITALIC = "italic";
|
||||||
|
private const string MARKDOWN_HEADING = "heading";
|
||||||
|
private const string MARKDOWN_BULLET_LIST = "bullet_list";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ChatThread? ChatThread { get; set; }
|
public ChatThread? ChatThread { get; set; }
|
||||||
|
|
||||||
@ -37,6 +44,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
[Inject]
|
[Inject]
|
||||||
private IDialogService DialogService { get; init; } = null!;
|
private IDialogService DialogService { get; init; } = null!;
|
||||||
|
|
||||||
|
[Inject]
|
||||||
|
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||||
|
|
||||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
||||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||||
|
|
||||||
@ -73,6 +83,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
|
|
||||||
// Configure the spellchecking for the user input:
|
// Configure the spellchecking for the user input:
|
||||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||||
|
USER_INPUT_ATTRIBUTES["id"] = CHAT_INPUT_ID;
|
||||||
|
|
||||||
// Get the preselected profile:
|
// Get the preselected profile:
|
||||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
|
||||||
@ -464,6 +475,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplyMarkdownFormat(string formatType)
|
||||||
|
{
|
||||||
|
if (this.IsInputForbidden())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(this.dataSourceSelectionComponent?.IsVisible ?? false)
|
||||||
|
this.dataSourceSelectionComponent.Hide();
|
||||||
|
|
||||||
|
this.userInput = await this.JsRuntime.InvokeAsync<string>("formatChatInputMarkdown", CHAT_INPUT_ID, formatType);
|
||||||
|
this.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SendMessage(bool reuseLastUserPrompt = false)
|
private async Task SendMessage(bool reuseLastUserPrompt = false)
|
||||||
{
|
{
|
||||||
if (!this.IsProviderSelected)
|
if (!this.IsProviderSelected)
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
<MudElement Class="@($"{this.ContainerClass} d-flex align-center")">
|
||||||
|
<MudText Typo="Typo.caption" Class="mb-0">
|
||||||
|
@this.Text
|
||||||
|
</MudText>
|
||||||
|
</MudElement>
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace AIStudio.Components;
|
||||||
|
|
||||||
|
public partial class HalluzinationReminder: ComponentBase
|
||||||
|
{
|
||||||
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(HalluzinationReminder).Namespace, nameof(HalluzinationReminder));
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Text { get; set; } = TB("LLMs can make mistakes. Check important information.");
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ContainerClass { get; set; } = "mt-2 mb-1";
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudIconButton Size="Size.Small" Icon="@Icons.Material.Filled.Person4" />
|
<MudIconButton Icon="@Icons.Material.Filled.Person4" Color="Color.Default" />
|
||||||
}
|
}
|
||||||
</ActivatorContent>
|
</ActivatorContent>
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
|
|||||||
@ -160,7 +160,7 @@
|
|||||||
<MudJustifiedText Class="mb-5">
|
<MudJustifiedText Class="mb-5">
|
||||||
@T("Please be aware: This section is for experts only. You are responsible for verifying the correctness of the additional parameters you provide to the API call. By default, AI Studio uses the OpenAI-compatible chat completions API, when that it is supported by the underlying service and model.")
|
@T("Please be aware: This section is for experts only. You are responsible for verifying the correctness of the additional parameters you provide to the API call. By default, AI Studio uses the OpenAI-compatible chat completions API, when that it is supported by the underlying service and model.")
|
||||||
</MudJustifiedText>
|
</MudJustifiedText>
|
||||||
<MudTextField T="string" Label=@T("Additional API parameters") Variant="Variant.Outlined" Lines="4" AutoGrow="true" MaxLines="10" HelperText=@T("""Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.""") Placeholder="@GetPlaceholderExpertSettings" @bind-Value="@this.AdditionalJsonApiParameters" OnBlur="@this.OnInputChangeExpertSettings"/>
|
<MudTextField T="string" Label=@T("Additional API parameters") Variant="Variant.Outlined" Lines="4" AutoGrow="true" MaxLines="10" HelperText=@T("""Add the parameters in proper JSON formatting, e.g., "temperature": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.""") Placeholder="@GetPlaceholderExpertSettings" @bind-Value="@this.AdditionalJsonApiParameters" Immediate="true" Validation="@this.ValidateAdditionalJsonApiParameters" OnBlur="@this.OnInputChangeExpertSettings"/>
|
||||||
</MudCollapse>
|
</MudCollapse>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudForm>
|
</MudForm>
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
using AIStudio.Components;
|
using AIStudio.Components;
|
||||||
using AIStudio.Provider;
|
using AIStudio.Provider;
|
||||||
using AIStudio.Provider.HuggingFace;
|
using AIStudio.Provider.HuggingFace;
|
||||||
@ -334,7 +337,168 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
|||||||
|
|
||||||
private void OnInputChangeExpertSettings()
|
private void OnInputChangeExpertSettings()
|
||||||
{
|
{
|
||||||
this.AdditionalJsonApiParameters = this.AdditionalJsonApiParameters.Trim().TrimEnd(',', ' ');
|
this.AdditionalJsonApiParameters = NormalizeAdditionalJsonApiParameters(this.AdditionalJsonApiParameters)
|
||||||
|
.Trim()
|
||||||
|
.TrimEnd(',', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ValidateAdditionalJsonApiParameters(string additionalParams)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(additionalParams))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var normalized = NormalizeAdditionalJsonApiParameters(additionalParams);
|
||||||
|
if (!string.Equals(normalized, additionalParams, StringComparison.Ordinal))
|
||||||
|
this.AdditionalJsonApiParameters = normalized;
|
||||||
|
|
||||||
|
var json = $"{{{normalized}}}";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!this.TryValidateJsonObjectWithDuplicateCheck(json, out var errorMessage))
|
||||||
|
return errorMessage;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return T("Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeAdditionalJsonApiParameters(string input)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(input.Length);
|
||||||
|
var inString = false;
|
||||||
|
var escape = false;
|
||||||
|
for (var i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
var c = input[i];
|
||||||
|
if (inString)
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
if (escape)
|
||||||
|
{
|
||||||
|
escape = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\\')
|
||||||
|
{
|
||||||
|
escape = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
|
inString = false;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
inString = true;
|
||||||
|
sb.Append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryReadToken(input, i, "True", out var tokenLength))
|
||||||
|
{
|
||||||
|
sb.Append("true");
|
||||||
|
i += tokenLength - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryReadToken(input, i, "False", out tokenLength))
|
||||||
|
{
|
||||||
|
sb.Append("false");
|
||||||
|
i += tokenLength - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryReadToken(input, i, "Null", out tokenLength))
|
||||||
|
{
|
||||||
|
sb.Append("null");
|
||||||
|
i += tokenLength - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadToken(string input, int startIndex, string token, out int tokenLength)
|
||||||
|
{
|
||||||
|
tokenLength = 0;
|
||||||
|
if (startIndex + token.Length > input.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!input.AsSpan(startIndex, token.Length).SequenceEqual(token))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var beforeIndex = startIndex - 1;
|
||||||
|
if (beforeIndex >= 0 && IsIdentifierChar(input[beforeIndex]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var afterIndex = startIndex + token.Length;
|
||||||
|
if (afterIndex < input.Length && IsIdentifierChar(input[afterIndex]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
tokenLength = token.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIdentifierChar(char c) => char.IsLetterOrDigit(c) || c == '_';
|
||||||
|
|
||||||
|
private bool TryValidateJsonObjectWithDuplicateCheck(string json, out string? errorMessage)
|
||||||
|
{
|
||||||
|
errorMessage = null;
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
var reader = new Utf8JsonReader(bytes, new JsonReaderOptions
|
||||||
|
{
|
||||||
|
AllowTrailingCommas = false,
|
||||||
|
CommentHandling = JsonCommentHandling.Disallow
|
||||||
|
});
|
||||||
|
|
||||||
|
var objectStack = new Stack<HashSet<string>>();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
switch (reader.TokenType)
|
||||||
|
{
|
||||||
|
case JsonTokenType.StartObject:
|
||||||
|
objectStack.Push(new HashSet<string>(StringComparer.Ordinal));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JsonTokenType.EndObject:
|
||||||
|
if (objectStack.Count > 0)
|
||||||
|
objectStack.Pop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JsonTokenType.PropertyName:
|
||||||
|
if (objectStack.Count == 0)
|
||||||
|
{
|
||||||
|
errorMessage = T("Additional API parameters must form a JSON object.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = reader.GetString() ?? string.Empty;
|
||||||
|
if (!objectStack.Peek().Add(name))
|
||||||
|
{
|
||||||
|
errorMessage = string.Format(T("Duplicate key '{0}' found."), name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectStack.Count != 0)
|
||||||
|
{
|
||||||
|
errorMessage = T("Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
||||||
|
|||||||
@ -97,7 +97,6 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
|
|
||||||
// Set the snackbar for the update service:
|
// Set the snackbar for the update service:
|
||||||
UpdateService.SetBlazorDependencies(this.Snackbar);
|
UpdateService.SetBlazorDependencies(this.Snackbar);
|
||||||
GlobalShortcutService.Initialize();
|
|
||||||
TemporaryChatService.Initialize();
|
TemporaryChatService.Initialize();
|
||||||
|
|
||||||
// Should the navigation bar be open by default?
|
// Should the navigation bar be open by default?
|
||||||
@ -251,6 +250,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
|||||||
|
|
||||||
// Set up hot reloading for plugins:
|
// Set up hot reloading for plugins:
|
||||||
PluginFactory.SetUpHotReloading();
|
PluginFactory.SetUpHotReloading();
|
||||||
|
await this.MessageBus.SendMessage<bool>(this, Event.STARTUP_COMPLETED);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -58,7 +58,9 @@ public partial class Information : MSGComponentBase
|
|||||||
|
|
||||||
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
|
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
|
||||||
|
|
||||||
private string VersionDatabase => $"{T("Database version")}: {this.DatabaseClient.Name} v{META_DATA_DATABASES.DatabaseVersion}";
|
private string VersionDatabase => this.DatabaseClient.IsAvailable
|
||||||
|
? $"{T("Database version")}: {this.DatabaseClient.Name} v{META_DATA_DATABASES.DatabaseVersion}"
|
||||||
|
: $"{T("Database")}: {this.DatabaseClient.Name} - {T("not available")}";
|
||||||
|
|
||||||
private string versionPandoc = TB("Determine Pandoc version, please wait...");
|
private string versionPandoc = TB("Determine Pandoc version, please wait...");
|
||||||
private PandocInstallation pandocInstallation;
|
private PandocInstallation pandocInstallation;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
SVG = [[<svg viewBox="0 0 600 600" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
SVG = [[<svg viewBox="0 0 600 600" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
||||||
<path style="fill:none" d="M0 0h600v600H0z"/>
|
<path style="fill:none" d="M0 0h600v600H0z"/>
|
||||||
<clipPath id="a">
|
<clipPath id="clip-de">
|
||||||
<path d="M0 0h600v600H0z"/>
|
<path d="M0 0h600v600H0z"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<g clip-path="url(#a)">
|
<g clip-path="url(#clip-de)">
|
||||||
<path d="M17.467 200C58.688 83.529 169.851 0 300.369 0S542.05 83.529 583.271 200c11.072 31.284 17.098 64.944 17.098 100s-6.026 68.716-17.098 100C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400C6.395 368.716.369 335.056.369 300s6.026-68.716 17.098-100Z"/>
|
<path d="M17.467 200C58.688 83.529 169.851 0 300.369 0S542.05 83.529 583.271 200c11.072 31.284 17.098 64.944 17.098 100s-6.026 68.716-17.098 100C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400C6.395 368.716.369 335.056.369 300s6.026-68.716 17.098-100Z"/>
|
||||||
<path d="M583.271 200c11.072 31.284 17.098 64.944 17.098 100s-6.026 68.716-17.098 100C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400C6.395 368.716.369 335.056.369 300s6.026-68.716 17.098-100h565.804Z" style="fill:#d00"/>
|
<path d="M583.271 200c11.072 31.284 17.098 64.944 17.098 100s-6.026 68.716-17.098 100C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400C6.395 368.716.369 335.056.369 300s6.026-68.716 17.098-100h565.804Z" style="fill:#d00"/>
|
||||||
<path d="M583.271 400C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400h565.804Z" style="fill:#ffce00"/>
|
<path d="M583.271 400C542.05 516.471 430.887 600 300.369 600S58.688 516.471 17.467 400h565.804Z" style="fill:#ffce00"/>
|
||||||
|
|||||||
@ -1291,7 +1291,7 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1714063121"] = "Satzstruktur"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1714063121"] = "Satzstruktur"
|
||||||
|
|
||||||
-- Rewrite & Improve Text
|
-- Rewrite & Improve Text
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1994150308"] = "Text umschreiben & verbessern"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1994150308"] = "Text umformulieren & verbessern"
|
||||||
|
|
||||||
-- Improve your text
|
-- Improve your text
|
||||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2163831433"] = "Verbessern Sie ihren Text"
|
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2163831433"] = "Verbessern Sie ihren Text"
|
||||||
@ -1593,6 +1593,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Chat vers
|
|||||||
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Sind Sie sicher, dass Sie diesen Chat verschieben möchten? Alle ungespeicherten Änderungen gehen verloren."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Sind Sie sicher, dass Sie diesen Chat verschieben möchten? Alle ungespeicherten Änderungen gehen verloren."
|
||||||
|
|
||||||
|
-- Bold
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1165397398"] = "Fett"
|
||||||
|
|
||||||
-- Stop generation
|
-- Stop generation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Generierung stoppen"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Generierung stoppen"
|
||||||
|
|
||||||
@ -1605,9 +1608,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Geben Sie
|
|||||||
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Ihr Prompt (verwendete Instanz: '{0}', Anbieter: '{1}')"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Ihr Prompt (verwendete Instanz: '{0}', Anbieter: '{1}')"
|
||||||
|
|
||||||
|
-- Code
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||||
|
|
||||||
|
-- Italic
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Kursiv"
|
||||||
|
|
||||||
-- Profile usage is disabled according to your chat template settings.
|
-- Profile usage is disabled according to your chat template settings.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Die Profilnutzung ist gemäß den Einstellungen ihrer Chat-Vorlage deaktiviert."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Die Profilnutzung ist gemäß den Einstellungen ihrer Chat-Vorlage deaktiviert."
|
||||||
|
|
||||||
|
-- Bulleted List
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Aufzählungszeichen"
|
||||||
|
|
||||||
-- Delete this chat & start a new one.
|
-- Delete this chat & start a new one.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Diesen Chat löschen & einen neuen beginnen."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Diesen Chat löschen & einen neuen beginnen."
|
||||||
|
|
||||||
@ -1626,6 +1638,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Neuen Cha
|
|||||||
-- New disappearing chat
|
-- New disappearing chat
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Neuen selbstlöschenden Chat starten"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Neuen selbstlöschenden Chat starten"
|
||||||
|
|
||||||
|
-- Heading
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Überschrift"
|
||||||
|
|
||||||
-- Please select the workspace where you want to move the chat to.
|
-- Please select the workspace where you want to move the chat to.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Bitte wählen Sie den Arbeitsbereich aus, in den Sie den Chat verschieben möchten."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Bitte wählen Sie den Arbeitsbereich aus, in den Sie den Chat verschieben möchten."
|
||||||
|
|
||||||
@ -1770,6 +1785,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T700666808"] = "Date
|
|||||||
-- Available Data Sources
|
-- Available Data Sources
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Verfügbare Datenquellen"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Verfügbare Datenquellen"
|
||||||
|
|
||||||
|
-- LLMs can make mistakes. Check important information.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs können Fehler machen. Überprüfen Sie wichtige Informationen."
|
||||||
|
|
||||||
-- Issues
|
-- Issues
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Probleme"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Probleme"
|
||||||
|
|
||||||
@ -2500,7 +2518,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2868740431"] = "Spezifische Anfo
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2899555955"] = "Wir werden weitere Assistenten für alltägliche Aufgaben entwickeln."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2899555955"] = "Wir werden weitere Assistenten für alltägliche Aufgaben entwickeln."
|
||||||
|
|
||||||
-- We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.
|
-- We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T308543246"] = "Wir arbeiten daran, die Funktionen von AI Studio über ein Plugin auch in ihrem Browser anzubieten. So können Sie zum Beispiel direkt im Browser Rechtschreibprüfungen durchführen oder Texte umschreiben lassen."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T308543246"] = "Wir arbeiten daran, die Funktionen von AI Studio über ein Plugin auch in ihrem Browser anzubieten. So können Sie zum Beispiel direkt im Browser Rechtschreibprüfungen durchführen oder Texte umformulieren lassen."
|
||||||
|
|
||||||
-- There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers.
|
-- There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3290746961"] = "Es wird eine Schnittstelle für AI Studio geben, um Inhalte in anderen Apps zu erstellen. So könnten Sie zum Beispiel Blogbeiträge direkt auf der Zielplattform verfassen oder Einträge zu einem internen Wissensmanagement-Tool hinzufügen. Dafür ist Entwicklungsarbeit durch die jeweiligen Tool-Entwickler erforderlich."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3290746961"] = "Es wird eine Schnittstelle für AI Studio geben, um Inhalte in anderen Apps zu erstellen. So könnten Sie zum Beispiel Blogbeiträge direkt auf der Zielplattform verfassen oder Einträge zu einem internen Wissensmanagement-Tool hinzufügen. Dafür ist Entwicklungsarbeit durch die jeweiligen Tool-Entwickler erforderlich."
|
||||||
@ -3534,6 +3552,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Der API-Sch
|
|||||||
-- Please enter a model name.
|
-- Please enter a model name.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Bitte geben Sie einen Modellnamen ein."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Bitte geben Sie einen Modellnamen ein."
|
||||||
|
|
||||||
|
-- Additional API parameters must form a JSON object.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Zusätzliche API-Parameter müssen ein JSON-Objekt bilden."
|
||||||
|
|
||||||
-- Model
|
-- Model
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Modell"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Modell"
|
||||||
|
|
||||||
@ -3555,12 +3576,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instanzname
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
|
||||||
|
|
||||||
|
-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Ungültiges JSON: Fügen Sie die Parameter in korrektem JSON-Format hinzu, z. B. \"temperature\": 0.5. Entfernen Sie abschließende Kommas. Die üblichen umgebenden geschweiften Klammern {} dürfen jedoch nicht verwendet werden."
|
||||||
|
|
||||||
-- Show available models
|
-- Show available models
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen"
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Es ist keine Modellauswahl verfügbar."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Es ist keine Modellauswahl verfügbar."
|
||||||
|
|
||||||
|
-- Duplicate key '{0}' found.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Doppelter Schlüssel '{0}' gefunden."
|
||||||
|
|
||||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Derzeit können wir die Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Derzeit können wir die Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
|
||||||
|
|
||||||
@ -4429,7 +4456,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1462295644
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1621537655"] = "Satzstruktur vorauswählen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1621537655"] = "Satzstruktur vorauswählen"
|
||||||
|
|
||||||
-- Assistant: Rewrite & Improve Text Options
|
-- Assistant: Rewrite & Improve Text Options
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1995708818"] = "Assistent: Text umschreiben & verbessern"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1995708818"] = "Assistent: Text umformulieren & verbessern"
|
||||||
|
|
||||||
-- Which voice should be preselected for the sentence structure?
|
-- Which voice should be preselected for the sentence structure?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T2661599097"] = "Welche Stimme soll für die Satzstruktur vorausgewählt werden?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T2661599097"] = "Welche Stimme soll für die Satzstruktur vorausgewählt werden?"
|
||||||
@ -4438,7 +4465,7 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T2661599097
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T28456020"] = "Wählen Sie einen Schreibstil aus"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T28456020"] = "Wählen Sie einen Schreibstil aus"
|
||||||
|
|
||||||
-- Rewrite & improve text options are preselected
|
-- Rewrite & improve text options are preselected
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3303192024"] = "Optionen für „Text umschreiben & verbessern“ sind bereits vorausgewählt."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3303192024"] = "Optionen für „Text umformulieren & verbessern“ sind bereits vorausgewählt."
|
||||||
|
|
||||||
-- Close
|
-- Close
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3448155331"] = "Schließen"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3448155331"] = "Schließen"
|
||||||
@ -4447,13 +4474,13 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3448155331
|
|||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3547337928"] = "Welche Zielsprache soll vorausgewählt werden?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3547337928"] = "Welche Zielsprache soll vorausgewählt werden?"
|
||||||
|
|
||||||
-- When enabled, you can preselect the rewrite & improve text options. This is might be useful when you prefer a specific language or LLM model.
|
-- When enabled, you can preselect the rewrite & improve text options. This is might be useful when you prefer a specific language or LLM model.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3657121735"] = "Wenn diese Option aktiviert ist, können Sie Optionen für „Text umschreiben & verbessern“ im Voraus auswählen. Das kann hilfreich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3657121735"] = "Wenn diese Option aktiviert ist, können Sie Optionen für „Text umformulieren & verbessern“ im Voraus auswählen. Das kann hilfreich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen."
|
||||||
|
|
||||||
-- Preselect rewrite & improve text options?
|
-- Preselect rewrite & improve text options?
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3745021518"] = "Vorauswahl von Optionen für „Text umschreiben & verbessern“?"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3745021518"] = "Vorauswahl von Optionen für „Text umformulieren & verbessern“?"
|
||||||
|
|
||||||
-- No rewrite & improve text options are preselected
|
-- No rewrite & improve text options are preselected
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T553954963"] = "Keine Optionen für „Text umschreiben & verbessern“ sind vorausgewählt"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T553954963"] = "Keine Optionen für „Text umformulieren & verbessern“ sind vorausgewählt"
|
||||||
|
|
||||||
-- When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model.
|
-- When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T183953912"] = "Wenn diese Option aktiviert ist, können Sie Synonymoptionen im Voraus auswählen. Dies kann nützlich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T183953912"] = "Wenn diese Option aktiviert ist, können Sie Synonymoptionen im Voraus auswählen. Dies kann nützlich sein, wenn Sie eine bestimmte Sprache oder ein bestimmtes LLM-Modell bevorzugen."
|
||||||
@ -5292,6 +5319,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri wird verwe
|
|||||||
-- Motivation
|
-- Motivation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
||||||
|
|
||||||
|
-- not available
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3574465749"] = "nicht verfügbar"
|
||||||
|
|
||||||
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "Diese Bibliothek wird verwendet, um Excel- und OpenDocument-Tabellendateien zu lesen. Dies ist zum Beispiel notwendig, wenn Tabellen als Datenquelle für einen Chat verwendet werden sollen."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "Diese Bibliothek wird verwendet, um Excel- und OpenDocument-Tabellendateien zu lesen. Dies ist zum Beispiel notwendig, wenn Tabellen als Datenquelle für einen Chat verwendet werden sollen."
|
||||||
|
|
||||||
@ -5313,6 +5343,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3986423270"] = "Pandoc-Installat
|
|||||||
-- Versions
|
-- Versions
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versionen"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versionen"
|
||||||
|
|
||||||
|
-- Database
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4036243672"] = "Datenbank"
|
||||||
|
|
||||||
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "Diese Bibliothek wird verwendet, um asynchrone Datenströme in Rust zu erstellen. Sie ermöglicht es uns, mit Datenströmen zu arbeiten, die asynchron bereitgestellt werden, wodurch sich Ereignisse oder Daten, die nach und nach eintreffen, leichter verarbeiten lassen. Wir nutzen dies zum Beispiel, um beliebige Daten aus dem Dateisystem an das Einbettungssystem zu übertragen."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "Diese Bibliothek wird verwendet, um asynchrone Datenströme in Rust zu erstellen. Sie ermöglicht es uns, mit Datenströmen zu arbeiten, die asynchron bereitgestellt werden, wodurch sich Ereignisse oder Daten, die nach und nach eintreffen, leichter verarbeiten lassen. Wir nutzen dies zum Beispiel, um beliebige Daten aus dem Dateisystem an das Einbettungssystem zu übertragen."
|
||||||
|
|
||||||
@ -5898,6 +5931,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T3893997203"] = "
|
|||||||
-- Trust all LLM providers
|
-- Trust all LLM providers
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Allen LLM-Anbietern vertrauen"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Allen LLM-Anbietern vertrauen"
|
||||||
|
|
||||||
|
-- Reason
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T1093747001"] = "Grund"
|
||||||
|
|
||||||
|
-- Unavailable
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T3662391977"] = "Nicht verfügbar"
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T6222351"] = "Status"
|
||||||
|
|
||||||
-- Storage size
|
-- Storage size
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Speichergröße"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Speichergröße"
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
SVG = [[<svg viewBox="0 0 650 650" style="fill-rule:evenodd;clip-rule:evenodd">
|
SVG = [[<svg viewBox="0 0 650 650" style="fill-rule:evenodd;clip-rule:evenodd">
|
||||||
<path style="fill:none" d="M0 0h650v650H0z"/>
|
<path style="fill:none" d="M0 0h650v650H0z"/>
|
||||||
<clipPath id="a">
|
<clipPath id="clip-en-us">
|
||||||
<path d="M0 0h650v650H0z"/>
|
<path d="M0 0h650v650H0z"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<g clip-path="url(#a)">
|
<g clip-path="url(#clip-en-us)">
|
||||||
<path d="M208.018 21.723C244.323 7.694 283.77 0 325 0c44.15 0 86.256 8.823 124.649 24.8a319.796 319.796 0 0 1 9.251 4.02A324.269 324.269 0 0 1 494 47.402 326.269 326.269 0 0 1 532.582 75c33.111 27.531 60.675 61.509 80.757 100a322.631 322.631 0 0 1 32.831 100 326.842 326.842 0 0 1 3.83 50c0 17.002-1.308 33.7-3.83 50a322.631 322.631 0 0 1-32.831 100c-20.082 38.491-47.646 72.469-80.757 100-56.319 46.827-128.686 75-207.582 75-78.896 0-151.263-28.173-207.582-75-33.111-27.531-60.675-61.509-80.757-100A322.631 322.631 0 0 1 3.83 375a325.032 325.032 0 0 1-2.881-25A328.869 328.869 0 0 1 0 325c0-17.002 1.308-33.7 3.83-50a322.493 322.493 0 0 1 31.196-96.822 323.44 323.44 0 0 1 8.553-15.752C62.893 129.089 87.96 99.493 117.418 75A325.476 325.476 0 0 1 191.1 28.82a324.203 324.203 0 0 1 16.918-7.097Z" style="fill:#b31942;fill-rule:nonzero"/>
|
<path d="M208.018 21.723C244.323 7.694 283.77 0 325 0c44.15 0 86.256 8.823 124.649 24.8a319.796 319.796 0 0 1 9.251 4.02A324.269 324.269 0 0 1 494 47.402 326.269 326.269 0 0 1 532.582 75c33.111 27.531 60.675 61.509 80.757 100a322.631 322.631 0 0 1 32.831 100 326.842 326.842 0 0 1 3.83 50c0 17.002-1.308 33.7-3.83 50a322.631 322.631 0 0 1-32.831 100c-20.082 38.491-47.646 72.469-80.757 100-56.319 46.827-128.686 75-207.582 75-78.896 0-151.263-28.173-207.582-75-33.111-27.531-60.675-61.509-80.757-100A322.631 322.631 0 0 1 3.83 375a325.032 325.032 0 0 1-2.881-25A328.869 328.869 0 0 1 0 325c0-17.002 1.308-33.7 3.83-50a322.493 322.493 0 0 1 31.196-96.822 323.44 323.44 0 0 1 8.553-15.752C62.893 129.089 87.96 99.493 117.418 75A325.476 325.476 0 0 1 191.1 28.82a324.203 324.203 0 0 1 16.918-7.097Z" style="fill:#b31942;fill-rule:nonzero"/>
|
||||||
<path d="M0 75h1235m0 100H0m0 100h1235m0 100H0m0 100h1235m0 100H0" style="fill-rule:nonzero;stroke:#fff;stroke-width:50px"/>
|
<path d="M0 75h1235m0 100H0m0 100h1235m0 100H0m0 100h1235m0 100H0" style="fill-rule:nonzero;stroke:#fff;stroke-width:50px"/>
|
||||||
<path d="M208.018 21.723C244.323 7.694 283.77 0 325 0c44.15 0 86.256 8.823 124.649 24.8a319.796 319.796 0 0 1 9.251 4.02A324.269 324.269 0 0 1 494 47.402V350H.949A328.869 328.869 0 0 1 0 325c0-17.002 1.308-33.7 3.83-50a322.493 322.493 0 0 1 31.196-96.822 323.44 323.44 0 0 1 8.553-15.752C62.893 129.089 87.96 99.493 117.418 75A325.476 325.476 0 0 1 191.1 28.82a324.203 324.203 0 0 1 16.918-7.097Z" style="fill:#0a3161;fill-rule:nonzero"/>
|
<path d="M208.018 21.723C244.323 7.694 283.77 0 325 0c44.15 0 86.256 8.823 124.649 24.8a319.796 319.796 0 0 1 9.251 4.02A324.269 324.269 0 0 1 494 47.402V350H.949A328.869 328.869 0 0 1 0 325c0-17.002 1.308-33.7 3.83-50a322.493 322.493 0 0 1 31.196-96.822 323.44 323.44 0 0 1 8.553-15.752C62.893 129.089 87.96 99.493 117.418 75A325.476 325.476 0 0 1 191.1 28.82a324.203 324.203 0 0 1 16.918-7.097Z" style="fill:#0a3161;fill-rule:nonzero"/>
|
||||||
|
|||||||
@ -1593,6 +1593,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Move chat
|
|||||||
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
-- Are you sure you want to move this chat? All unsaved changes will be lost.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost."
|
||||||
|
|
||||||
|
-- Bold
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1165397398"] = "Bold"
|
||||||
|
|
||||||
-- Stop generation
|
-- Stop generation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation"
|
||||||
|
|
||||||
@ -1605,9 +1608,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your
|
|||||||
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
-- Your Prompt (use selected instance '{0}', provider '{1}')
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')"
|
||||||
|
|
||||||
|
-- Code
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2036185364"] = "Code"
|
||||||
|
|
||||||
|
-- Italic
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2377171085"] = "Italic"
|
||||||
|
|
||||||
-- Profile usage is disabled according to your chat template settings.
|
-- Profile usage is disabled according to your chat template settings.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."
|
||||||
|
|
||||||
|
-- Bulleted List
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2957125464"] = "Bulleted List"
|
||||||
|
|
||||||
-- Delete this chat & start a new one.
|
-- Delete this chat & start a new one.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one."
|
||||||
|
|
||||||
@ -1626,6 +1638,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3928697643"] = "Start new
|
|||||||
-- New disappearing chat
|
-- New disappearing chat
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "New disappearing chat"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "New disappearing chat"
|
||||||
|
|
||||||
|
-- Heading
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4231005109"] = "Heading"
|
||||||
|
|
||||||
-- Please select the workspace where you want to move the chat to.
|
-- Please select the workspace where you want to move the chat to.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to."
|
||||||
|
|
||||||
@ -1770,6 +1785,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T700666808"] = "Mana
|
|||||||
-- Available Data Sources
|
-- Available Data Sources
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources"
|
||||||
|
|
||||||
|
-- LLMs can make mistakes. Check important information.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::HALLUZINATIONREMINDER::T3528806904"] = "LLMs can make mistakes. Check important information."
|
||||||
|
|
||||||
-- Issues
|
-- Issues
|
||||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
|
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues"
|
||||||
|
|
||||||
@ -3534,6 +3552,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Failed to l
|
|||||||
-- Please enter a model name.
|
-- Please enter a model name.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
|
||||||
|
|
||||||
|
-- Additional API parameters must form a JSON object.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Additional API parameters must form a JSON object."
|
||||||
|
|
||||||
-- Model
|
-- Model
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
|
||||||
|
|
||||||
@ -3555,12 +3576,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Na
|
|||||||
-- Show Expert Settings
|
-- Show Expert Settings
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \\\"temperature\\\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
|
||||||
|
|
||||||
-- Show available models
|
-- Show available models
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
||||||
|
|
||||||
-- This host uses the model configured at the provider level. No model selection is available.
|
-- This host uses the model configured at the provider level. No model selection is available.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||||
|
|
||||||
|
-- Duplicate key '{0}' found.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Duplicate key '{0}' found."
|
||||||
|
|
||||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
||||||
|
|
||||||
@ -5292,6 +5319,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to
|
|||||||
-- Motivation
|
-- Motivation
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
||||||
|
|
||||||
|
-- not available
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3574465749"] = "not available"
|
||||||
|
|
||||||
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."
|
||||||
|
|
||||||
@ -5313,6 +5343,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3986423270"] = "Check Pandoc Ins
|
|||||||
-- Versions
|
-- Versions
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versions"
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4010195468"] = "Versions"
|
||||||
|
|
||||||
|
-- Database
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4036243672"] = "Database"
|
||||||
|
|
||||||
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system."
|
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system."
|
||||||
|
|
||||||
@ -5898,6 +5931,15 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T3893997203"] = "
|
|||||||
-- Trust all LLM providers
|
-- Trust all LLM providers
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
||||||
|
|
||||||
|
-- Reason
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T1093747001"] = "Reason"
|
||||||
|
|
||||||
|
-- Unavailable
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T3662391977"] = "Unavailable"
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::NODATABASECLIENT::T6222351"] = "Status"
|
||||||
|
|
||||||
-- Storage size
|
-- Storage size
|
||||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
||||||
|
|
||||||
|
|||||||
@ -86,6 +86,14 @@ internal sealed class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
var qdrantInfo = await rust.GetQdrantInfo();
|
var qdrantInfo = await rust.GetQdrantInfo();
|
||||||
|
DatabaseClient databaseClient;
|
||||||
|
if (!qdrantInfo.IsAvailable)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Warning: Qdrant is not available. Starting without vector database. Reason: '{qdrantInfo.UnavailableReason ?? "unknown"}'.");
|
||||||
|
databaseClient = new NoDatabaseClient("Qdrant", qdrantInfo.UnavailableReason);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (qdrantInfo.Path == string.Empty)
|
if (qdrantInfo.Path == string.Empty)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: Failed to get the Qdrant path from Rust.");
|
Console.WriteLine("Error: Failed to get the Qdrant path from Rust.");
|
||||||
@ -116,7 +124,8 @@ internal sealed class Program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var databaseClient = new QdrantClientImplementation("Qdrant", qdrantInfo.Path, qdrantInfo.PortHttp, qdrantInfo.PortGrpc, qdrantInfo.Fingerprint, qdrantInfo.ApiToken);
|
databaseClient = new QdrantClientImplementation("Qdrant", qdrantInfo.Path, qdrantInfo.PortHttp, qdrantInfo.PortGrpc, qdrantInfo.Fingerprint, qdrantInfo.ApiToken);
|
||||||
|
}
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder();
|
var builder = WebApplication.CreateBuilder();
|
||||||
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
||||||
|
|||||||
@ -29,6 +29,9 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
|
|||||||
|
|
||||||
// Parse the API parameters:
|
// Parse the API parameters:
|
||||||
var apiParameters = this.ParseAdditionalApiParameters("system");
|
var apiParameters = this.ParseAdditionalApiParameters("system");
|
||||||
|
var maxTokens = 4_096;
|
||||||
|
if (TryPopIntParameter(apiParameters, "max_tokens", out var parsedMaxTokens))
|
||||||
|
maxTokens = parsedMaxTokens;
|
||||||
|
|
||||||
// Build the list of messages:
|
// Build the list of messages:
|
||||||
var messages = await chatThread.Blocks.BuildMessagesAsync(
|
var messages = await chatThread.Blocks.BuildMessagesAsync(
|
||||||
@ -73,7 +76,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
|
|||||||
Messages = [..messages],
|
Messages = [..messages],
|
||||||
|
|
||||||
System = chatThread.PrepareSystemPrompt(settingsManager),
|
System = chatThread.PrepareSystemPrompt(settingsManager),
|
||||||
MaxTokens = apiParameters.TryGetValue("max_tokens", out var value) && value is int intValue ? intValue : 4_096,
|
MaxTokens = maxTokens,
|
||||||
|
|
||||||
// Right now, we only support streaming completions:
|
// Right now, we only support streaming completions:
|
||||||
Stream = true,
|
Stream = true,
|
||||||
|
|||||||
@ -731,7 +731,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
|||||||
/// <param name="keysToRemove">Optional list of keys to remove from the final dictionary
|
/// <param name="keysToRemove">Optional list of keys to remove from the final dictionary
|
||||||
/// (case-insensitive). The parameters stream, model, and messages are removed by default.</param>
|
/// (case-insensitive). The parameters stream, model, and messages are removed by default.</param>
|
||||||
protected IDictionary<string, object> ParseAdditionalApiParameters(
|
protected IDictionary<string, object> ParseAdditionalApiParameters(
|
||||||
params List<string> keysToRemove)
|
params string[] keysToRemove)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters))
|
if(string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters))
|
||||||
return new Dictionary<string, object>();
|
return new Dictionary<string, object>();
|
||||||
@ -744,14 +744,23 @@ public abstract class BaseProvider : IProvider, ISecretId
|
|||||||
var dict = ConvertToDictionary(jsonDoc);
|
var dict = ConvertToDictionary(jsonDoc);
|
||||||
|
|
||||||
// Some keys are always removed because we set them:
|
// Some keys are always removed because we set them:
|
||||||
keysToRemove.Add("stream");
|
var removeSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
keysToRemove.Add("model");
|
if (keysToRemove.Length > 0)
|
||||||
keysToRemove.Add("messages");
|
removeSet.UnionWith(keysToRemove);
|
||||||
|
|
||||||
|
removeSet.Add("stream");
|
||||||
|
removeSet.Add("model");
|
||||||
|
removeSet.Add("messages");
|
||||||
|
|
||||||
// Remove the specified keys (case-insensitive):
|
// Remove the specified keys (case-insensitive):
|
||||||
var removeSet = new HashSet<string>(keysToRemove, StringComparer.OrdinalIgnoreCase);
|
if (removeSet.Count > 0)
|
||||||
foreach (var key in removeSet)
|
{
|
||||||
|
foreach (var key in dict.Keys.ToList())
|
||||||
|
{
|
||||||
|
if (removeSet.Contains(key))
|
||||||
dict.Remove(key);
|
dict.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
@ -762,6 +771,85 @@ public abstract class BaseProvider : IProvider, ISecretId
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static bool TryPopIntParameter(IDictionary<string, object> parameters, string key, out int value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (!TryPopParameter(parameters, key, out var raw) || raw is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (raw)
|
||||||
|
{
|
||||||
|
case int i:
|
||||||
|
value = i;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case long l when l is >= int.MinValue and <= int.MaxValue:
|
||||||
|
value = (int)l;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case double d when d is >= int.MinValue and <= int.MaxValue:
|
||||||
|
value = (int)d;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case decimal m when m is >= int.MinValue and <= int.MaxValue:
|
||||||
|
value = (int)m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static bool TryPopBoolParameter(IDictionary<string, object> parameters, string key, out bool value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (!TryPopParameter(parameters, key, out var raw) || raw is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (raw)
|
||||||
|
{
|
||||||
|
case bool b:
|
||||||
|
value = b;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case string s when bool.TryParse(s, out var parsed):
|
||||||
|
value = parsed;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case int i:
|
||||||
|
value = i != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case long l:
|
||||||
|
value = l != 0;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case double d:
|
||||||
|
value = Math.Abs(d) > double.Epsilon;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case decimal m:
|
||||||
|
value = m != 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryPopParameter(IDictionary<string, object> parameters, string key, out object? value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (parameters.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var foundKey = parameters.Keys.FirstOrDefault(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (foundKey is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = parameters[foundKey];
|
||||||
|
parameters.Remove(foundKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static IDictionary<string, object> ConvertToDictionary(JsonElement element)
|
private static IDictionary<string, object> ConvertToDictionary(JsonElement element)
|
||||||
{
|
{
|
||||||
return element.EnumerateObject()
|
return element.EnumerateObject()
|
||||||
|
|||||||
@ -14,7 +14,8 @@ public readonly record struct ChatRequest(
|
|||||||
string Model,
|
string Model,
|
||||||
IList<IMessageBase> Messages,
|
IList<IMessageBase> Messages,
|
||||||
bool Stream,
|
bool Stream,
|
||||||
int RandomSeed,
|
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
int? RandomSeed,
|
||||||
bool SafePrompt = false
|
bool SafePrompt = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -36,6 +36,8 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
|||||||
|
|
||||||
// Parse the API parameters:
|
// Parse the API parameters:
|
||||||
var apiParameters = this.ParseAdditionalApiParameters();
|
var apiParameters = this.ParseAdditionalApiParameters();
|
||||||
|
var safePrompt = TryPopBoolParameter(apiParameters, "safe_prompt", out var parsedSafePrompt) && parsedSafePrompt;
|
||||||
|
var randomSeed = TryPopIntParameter(apiParameters, "random_seed", out var parsedRandomSeed) ? parsedRandomSeed : (int?)null;
|
||||||
|
|
||||||
// Build the list of messages:
|
// Build the list of messages:
|
||||||
var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
|
var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
|
||||||
@ -52,7 +54,8 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
|||||||
|
|
||||||
// Right now, we only support streaming completions:
|
// Right now, we only support streaming completions:
|
||||||
Stream = true,
|
Stream = true,
|
||||||
SafePrompt = apiParameters.TryGetValue("safe_prompt", out var value) && value is true,
|
RandomSeed = randomSeed,
|
||||||
|
SafePrompt = safePrompt,
|
||||||
AdditionalApiParameters = apiParameters
|
AdditionalApiParameters = apiParameters
|
||||||
}, JSON_SERIALIZER_OPTIONS);
|
}, JSON_SERIALIZER_OPTIONS);
|
||||||
|
|
||||||
|
|||||||
@ -253,7 +253,7 @@ public sealed record Provider(
|
|||||||
["AdditionalJsonApiParameters"] = "{{LuaTools.EscapeLuaString(this.AdditionalJsonApiParameters)}}",
|
["AdditionalJsonApiParameters"] = "{{LuaTools.EscapeLuaString(this.AdditionalJsonApiParameters)}}",
|
||||||
["Model"] = {
|
["Model"] = {
|
||||||
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}",
|
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}",
|
||||||
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? string.Empty)}}",
|
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? this.Model.Id)}}",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|||||||
@ -24,6 +24,17 @@ public static partial class ProviderExtensions
|
|||||||
Capability.CHAT_COMPLETION_API,
|
Capability.CHAT_COMPLETION_API,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Check for Qwen 3.5:
|
||||||
|
if(modelName.StartsWith("qwen3.5"))
|
||||||
|
return
|
||||||
|
[
|
||||||
|
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||||
|
Capability.TEXT_OUTPUT,
|
||||||
|
|
||||||
|
Capability.OPTIONAL_REASONING, Capability.FUNCTION_CALLING,
|
||||||
|
Capability.CHAT_COMPLETION_API,
|
||||||
|
];
|
||||||
|
|
||||||
// Check for Qwen 3:
|
// Check for Qwen 3:
|
||||||
if(modelName.StartsWith("qwen3"))
|
if(modelName.StartsWith("qwen3"))
|
||||||
return
|
return
|
||||||
|
|||||||
@ -102,6 +102,17 @@ public static partial class ProviderExtensions
|
|||||||
Capability.CHAT_COMPLETION_API,
|
Capability.CHAT_COMPLETION_API,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Check for Qwen 3.5:
|
||||||
|
if(modelName.IndexOf("qwen3.5") is not -1)
|
||||||
|
return
|
||||||
|
[
|
||||||
|
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||||
|
Capability.TEXT_OUTPUT,
|
||||||
|
|
||||||
|
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING,
|
||||||
|
Capability.CHAT_COMPLETION_API,
|
||||||
|
];
|
||||||
|
|
||||||
if(modelName.IndexOf("-vl-") is not -1)
|
if(modelName.IndexOf("-vl-") is not -1)
|
||||||
return [
|
return [
|
||||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||||
|
|||||||
@ -63,18 +63,29 @@ public sealed class SettingsManager
|
|||||||
/// Loads the settings from the file system.
|
/// Loads the settings from the file system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task LoadSettings()
|
public async Task LoadSettings()
|
||||||
|
{
|
||||||
|
var settingsSnapshot = await this.TryReadSettingsSnapshot();
|
||||||
|
if (settingsSnapshot is not null)
|
||||||
|
this.ConfigurationData = settingsSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads the settings from disk without mutating the current in-memory state.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A (migrated) settings snapshot, or null if it could not be read.</returns>
|
||||||
|
public async Task<Data?> TryReadSettingsSnapshot()
|
||||||
{
|
{
|
||||||
if(!this.IsSetUp)
|
if(!this.IsSetUp)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Cannot load settings, because the configuration is not set up yet.");
|
this.logger.LogWarning("Cannot load settings, because the configuration is not set up yet.");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
|
||||||
if(!File.Exists(settingsPath))
|
if(!File.Exists(settingsPath))
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Cannot load settings, because the settings file does not exist.");
|
this.logger.LogWarning("Cannot load settings, because the settings file does not exist.");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We read the `"Version": "V3"` line to determine the version of the settings file:
|
// We read the `"Version": "V3"` line to determine the version of the settings file:
|
||||||
@ -93,24 +104,22 @@ public sealed class SettingsManager
|
|||||||
if(settingsVersion is Version.UNKNOWN)
|
if(settingsVersion is Version.UNKNOWN)
|
||||||
{
|
{
|
||||||
this.logger.LogError("Unknown version of the settings file found.");
|
this.logger.LogError("Unknown version of the settings file found.");
|
||||||
this.ConfigurationData = new();
|
return new();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ConfigurationData = SettingsMigrations.Migrate(this.logger, settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
|
var settingsData = SettingsMigrations.Migrate(this.logger, settingsVersion, await File.ReadAllTextAsync(settingsPath), JSON_OPTIONS);
|
||||||
|
|
||||||
//
|
//
|
||||||
// We filter the enabled preview features based on the preview visibility.
|
// We filter the enabled preview features based on the preview visibility.
|
||||||
// This is necessary when the app starts up: some preview features may have
|
// This is necessary when the app starts up: some preview features may have
|
||||||
// been disabled or released from the last time the app was started.
|
// been disabled or released from the last time the app was started.
|
||||||
//
|
//
|
||||||
this.ConfigurationData.App.EnabledPreviewFeatures = this.ConfigurationData.App.PreviewVisibility.FilterPreviewFeatures(this.ConfigurationData.App.EnabledPreviewFeatures);
|
settingsData.App.EnabledPreviewFeatures = settingsData.App.PreviewVisibility.FilterPreviewFeatures(settingsData.App.EnabledPreviewFeatures);
|
||||||
|
return settingsData;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.LogError("Failed to read the version of the settings file.");
|
this.logger.LogError("Failed to read the version of the settings file.");
|
||||||
this.ConfigurationData = new();
|
return new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -4,6 +4,8 @@ public abstract class DatabaseClient(string name, string path)
|
|||||||
{
|
{
|
||||||
public string Name => name;
|
public string Name => name;
|
||||||
|
|
||||||
|
public virtual bool IsAvailable => true;
|
||||||
|
|
||||||
private string Path => path;
|
private string Path => path;
|
||||||
|
|
||||||
private ILogger<DatabaseClient>? logger;
|
private ILogger<DatabaseClient>? logger;
|
||||||
|
|||||||
24
app/MindWork AI Studio/Tools/Databases/NoDatabaseClient.cs
Normal file
24
app/MindWork AI Studio/Tools/Databases/NoDatabaseClient.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using AIStudio.Tools.PluginSystem;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools.Databases;
|
||||||
|
|
||||||
|
public sealed class NoDatabaseClient(string name, string? unavailableReason) : DatabaseClient(name, string.Empty)
|
||||||
|
{
|
||||||
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(NoDatabaseClient).Namespace, nameof(NoDatabaseClient));
|
||||||
|
|
||||||
|
public override bool IsAvailable => false;
|
||||||
|
|
||||||
|
public override async IAsyncEnumerable<(string Label, string Value)> GetDisplayInfo()
|
||||||
|
{
|
||||||
|
yield return (TB("Status"), TB("Unavailable"));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(unavailableReason))
|
||||||
|
yield return (TB("Reason"), unavailableReason);
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ public class QdrantClientImplementation : DatabaseClient
|
|||||||
private async Task<string> GetVersion()
|
private async Task<string> GetVersion()
|
||||||
{
|
{
|
||||||
var operation = await this.GrpcClient.HealthAsync();
|
var operation = await this.GrpcClient.HealthAsync();
|
||||||
return "v"+operation.Version;
|
return $"v{operation.Version}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetCollectionsAmount()
|
private async Task<string> GetCollectionsAmount()
|
||||||
|
|||||||
@ -9,6 +9,7 @@ public enum Event
|
|||||||
CONFIGURATION_CHANGED,
|
CONFIGURATION_CHANGED,
|
||||||
COLOR_THEME_CHANGED,
|
COLOR_THEME_CHANGED,
|
||||||
STARTUP_PLUGIN_SYSTEM,
|
STARTUP_PLUGIN_SYSTEM,
|
||||||
|
STARTUP_COMPLETED,
|
||||||
STARTUP_ENTERPRISE_ENVIRONMENT,
|
STARTUP_ENTERPRISE_ENVIRONMENT,
|
||||||
PLUGINS_RELOADED,
|
PLUGINS_RELOADED,
|
||||||
SHOW_ERROR,
|
SHOW_ERROR,
|
||||||
|
|||||||
@ -13,6 +13,51 @@ public readonly record struct FileTypeFilter(string FilterName, string[] FilterE
|
|||||||
{
|
{
|
||||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileTypeFilter).Namespace, nameof(FileTypeFilter));
|
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileTypeFilter).Namespace, nameof(FileTypeFilter));
|
||||||
|
|
||||||
|
private static string[] AllowedSourceLikeFileNames =>
|
||||||
|
[
|
||||||
|
"Dockerfile",
|
||||||
|
"Containerfile",
|
||||||
|
"Jenkinsfile",
|
||||||
|
"Makefile",
|
||||||
|
"GNUmakefile",
|
||||||
|
"Procfile",
|
||||||
|
"Vagrantfile",
|
||||||
|
"Tiltfile",
|
||||||
|
"Justfile",
|
||||||
|
"Brewfile",
|
||||||
|
"Caddyfile",
|
||||||
|
"Gemfile",
|
||||||
|
"Podfile",
|
||||||
|
"Fastfile",
|
||||||
|
"Appfile",
|
||||||
|
"Rakefile",
|
||||||
|
"Dangerfile",
|
||||||
|
"BUILD",
|
||||||
|
"WORKSPACE",
|
||||||
|
"BUCK",
|
||||||
|
];
|
||||||
|
|
||||||
|
private static string[] AllowedSourceLikeFileNamePrefixes =>
|
||||||
|
[
|
||||||
|
"Dockerfile",
|
||||||
|
"Containerfile",
|
||||||
|
"Jenkinsfile",
|
||||||
|
"Procfile",
|
||||||
|
"Caddyfile",
|
||||||
|
];
|
||||||
|
|
||||||
|
public static bool IsAllowedSourceLikeFileName(string filePath)
|
||||||
|
{
|
||||||
|
var fileName = Path.GetFileName(filePath);
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (AllowedSourceLikeFileNames.Any(name => string.Equals(name, fileName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return AllowedSourceLikeFileNamePrefixes.Any(prefix => fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
public static FileTypeFilter PDF => new(TB("PDF Files"), ["pdf"]);
|
public static FileTypeFilter PDF => new(TB("PDF Files"), ["pdf"]);
|
||||||
|
|
||||||
public static FileTypeFilter Text => new(TB("Text Files"), ["txt", "md"]);
|
public static FileTypeFilter Text => new(TB("Text Files"), ["txt", "md"]);
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly record struct QdrantInfo
|
public readonly record struct QdrantInfo
|
||||||
{
|
{
|
||||||
|
public bool IsAvailable { get; init; }
|
||||||
|
|
||||||
|
public string? UnavailableReason { get; init; }
|
||||||
|
|
||||||
public string Path { get; init; }
|
public string Path { get; init; }
|
||||||
|
|
||||||
public int PortHttp { get; init; }
|
public int PortHttp { get; init; }
|
||||||
|
|||||||
@ -8,8 +8,16 @@ namespace AIStudio.Tools.Services;
|
|||||||
|
|
||||||
public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiver
|
public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiver
|
||||||
{
|
{
|
||||||
private static bool IS_INITIALIZED;
|
private static bool IS_STARTUP_COMPLETED;
|
||||||
|
|
||||||
|
private enum ShortcutSyncSource
|
||||||
|
{
|
||||||
|
CONFIGURATION_CHANGED,
|
||||||
|
STARTUP_COMPLETED,
|
||||||
|
PLUGINS_RELOADED,
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim registrationSemaphore = new(1, 1);
|
||||||
private readonly ILogger<GlobalShortcutService> logger;
|
private readonly ILogger<GlobalShortcutService> logger;
|
||||||
private readonly SettingsManager settingsManager;
|
private readonly SettingsManager settingsManager;
|
||||||
private readonly MessageBus messageBus;
|
private readonly MessageBus messageBus;
|
||||||
@ -27,22 +35,19 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
|||||||
this.rustService = rustService;
|
this.rustService = rustService;
|
||||||
|
|
||||||
this.messageBus.RegisterComponent(this);
|
this.messageBus.RegisterComponent(this);
|
||||||
this.ApplyFilters([], [Event.CONFIGURATION_CHANGED]);
|
this.ApplyFilters([], [Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED, Event.STARTUP_COMPLETED]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
// Wait until the app is fully initialized:
|
this.logger.LogInformation("The global shortcut service was initialized.");
|
||||||
while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
|
await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
|
|
||||||
|
|
||||||
// Register shortcuts on startup:
|
|
||||||
await this.RegisterAllShortcuts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.messageBus.Unregister(this);
|
this.messageBus.Unregister(this);
|
||||||
|
this.registrationSemaphore.Dispose();
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +58,22 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
|||||||
switch (triggeredEvent)
|
switch (triggeredEvent)
|
||||||
{
|
{
|
||||||
case Event.CONFIGURATION_CHANGED:
|
case Event.CONFIGURATION_CHANGED:
|
||||||
await this.RegisterAllShortcuts();
|
if (!IS_STARTUP_COMPLETED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await this.RegisterAllShortcuts(ShortcutSyncSource.CONFIGURATION_CHANGED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Event.STARTUP_COMPLETED:
|
||||||
|
IS_STARTUP_COMPLETED = true;
|
||||||
|
await this.RegisterAllShortcuts(ShortcutSyncSource.STARTUP_COMPLETED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Event.PLUGINS_RELOADED:
|
||||||
|
if (!IS_STARTUP_COMPLETED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await this.RegisterAllShortcuts(ShortcutSyncSource.PLUGINS_RELOADED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,16 +82,35 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private async Task RegisterAllShortcuts()
|
private async Task RegisterAllShortcuts(ShortcutSyncSource source)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Registering global shortcuts.");
|
await this.registrationSemaphore.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Registering global shortcuts (source='{Source}').", source);
|
||||||
foreach (var shortcutId in Enum.GetValues<Shortcut>())
|
foreach (var shortcutId in Enum.GetValues<Shortcut>())
|
||||||
{
|
{
|
||||||
if(shortcutId is Shortcut.NONE)
|
if(shortcutId is Shortcut.NONE)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var shortcut = this.GetShortcutValue(shortcutId);
|
var shortcutState = await this.GetShortcutState(shortcutId, source);
|
||||||
var isEnabled = this.IsShortcutAllowed(shortcutId);
|
var shortcut = shortcutState.Shortcut;
|
||||||
|
var isEnabled = shortcutState.IsEnabled;
|
||||||
|
this.logger.LogInformation(
|
||||||
|
"Sync shortcut '{ShortcutId}' (source='{Source}', enabled={IsEnabled}, configured='{Shortcut}').",
|
||||||
|
shortcutId,
|
||||||
|
source,
|
||||||
|
isEnabled,
|
||||||
|
shortcut);
|
||||||
|
|
||||||
|
if (shortcutState.UsesPersistedFallback)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning(
|
||||||
|
"Using persisted shortcut fallback for '{ShortcutId}' during startup completion (source='{Source}', configured='{Shortcut}').",
|
||||||
|
shortcutId,
|
||||||
|
source,
|
||||||
|
shortcut);
|
||||||
|
}
|
||||||
|
|
||||||
if (isEnabled && !string.IsNullOrWhiteSpace(shortcut))
|
if (isEnabled && !string.IsNullOrWhiteSpace(shortcut))
|
||||||
{
|
{
|
||||||
@ -83,12 +122,24 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
this.logger.LogInformation(
|
||||||
|
"Disabling global shortcut '{ShortcutId}' (source='{Source}', enabled={IsEnabled}, configured='{Shortcut}').",
|
||||||
|
shortcutId,
|
||||||
|
source,
|
||||||
|
isEnabled,
|
||||||
|
shortcut);
|
||||||
|
|
||||||
// Disable the shortcut when empty or feature is disabled:
|
// Disable the shortcut when empty or feature is disabled:
|
||||||
await this.rustService.UpdateGlobalShortcut(shortcutId, string.Empty);
|
await this.rustService.UpdateGlobalShortcut(shortcutId, string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.LogInformation("Global shortcuts registration completed.");
|
this.logger.LogInformation("Global shortcuts registration completed (source='{Source}').", source);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.registrationSemaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetShortcutValue(Shortcut name) => name switch
|
private string GetShortcutValue(Shortcut name) => name switch
|
||||||
@ -107,5 +158,30 @@ public sealed class GlobalShortcutService : BackgroundService, IMessageBusReceiv
|
|||||||
_ => true,
|
_ => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void Initialize() => IS_INITIALIZED = true;
|
private async Task<ShortcutState> GetShortcutState(Shortcut shortcutId, ShortcutSyncSource source)
|
||||||
|
{
|
||||||
|
var shortcut = this.GetShortcutValue(shortcutId);
|
||||||
|
var isEnabled = this.IsShortcutAllowed(shortcutId);
|
||||||
|
if (isEnabled && !string.IsNullOrWhiteSpace(shortcut))
|
||||||
|
return new(shortcut, true, false);
|
||||||
|
|
||||||
|
if (source is not ShortcutSyncSource.STARTUP_COMPLETED || shortcutId is not Shortcut.VOICE_RECORDING_TOGGLE)
|
||||||
|
return new(shortcut, isEnabled, false);
|
||||||
|
|
||||||
|
var settingsSnapshot = await this.settingsManager.TryReadSettingsSnapshot();
|
||||||
|
if (settingsSnapshot is null)
|
||||||
|
return new(shortcut, isEnabled, false);
|
||||||
|
|
||||||
|
var fallbackShortcut = settingsSnapshot.App.ShortcutVoiceRecording;
|
||||||
|
var fallbackEnabled =
|
||||||
|
settingsSnapshot.App.EnabledPreviewFeatures.Contains(PreviewFeatures.PRE_SPEECH_TO_TEXT_2026) &&
|
||||||
|
!string.IsNullOrWhiteSpace(settingsSnapshot.App.UseTranscriptionProvider);
|
||||||
|
|
||||||
|
if (!fallbackEnabled || string.IsNullOrWhiteSpace(fallbackShortcut))
|
||||||
|
return new(shortcut, isEnabled, false);
|
||||||
|
|
||||||
|
return new(fallbackShortcut, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct ShortcutState(string Shortcut, bool IsEnabled, bool UsesPersistedFallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,3 +26,108 @@ window.clearDiv = function (divName) {
|
|||||||
window.scrollToBottom = function(element) {
|
window.scrollToBottom = function(element) {
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
|
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.formatChatInputMarkdown = function (inputId, formatType) {
|
||||||
|
let input = document.getElementById(inputId)
|
||||||
|
if (input && input.tagName !== 'TEXTAREA' && input.tagName !== 'INPUT')
|
||||||
|
input = input.querySelector('textarea, input')
|
||||||
|
|
||||||
|
if (!input)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
input.focus()
|
||||||
|
|
||||||
|
const value = input.value ?? ''
|
||||||
|
const start = input.selectionStart ?? value.length
|
||||||
|
const end = input.selectionEnd ?? value.length
|
||||||
|
const hasSelection = end > start
|
||||||
|
const selectedText = value.substring(start, end)
|
||||||
|
|
||||||
|
let insertedText = ''
|
||||||
|
let selectionStart = start
|
||||||
|
let selectionEnd = start
|
||||||
|
|
||||||
|
switch (formatType) {
|
||||||
|
case 'bold': {
|
||||||
|
const text = hasSelection ? selectedText : ''
|
||||||
|
insertedText = `**${text}**`
|
||||||
|
selectionStart = start + 2
|
||||||
|
selectionEnd = selectionStart + text.length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'italic': {
|
||||||
|
const text = hasSelection ? selectedText : ''
|
||||||
|
insertedText = `*${text}*`
|
||||||
|
selectionStart = start + 1
|
||||||
|
selectionEnd = selectionStart + text.length
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'heading': {
|
||||||
|
if (hasSelection) {
|
||||||
|
insertedText = selectedText
|
||||||
|
.split('\n')
|
||||||
|
.map(line => line.startsWith('# ') ? line : `# ${line}`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
selectionStart = start
|
||||||
|
selectionEnd = start + insertedText.length
|
||||||
|
} else {
|
||||||
|
const text = ''
|
||||||
|
insertedText = `# ${text}`
|
||||||
|
selectionStart = start + 2
|
||||||
|
selectionEnd = selectionStart + text.length
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bullet_list': {
|
||||||
|
if (hasSelection) {
|
||||||
|
insertedText = selectedText
|
||||||
|
.split('\n')
|
||||||
|
.map(line => line.startsWith('- ') ? line : `- ${line}`)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
selectionStart = start
|
||||||
|
selectionEnd = start + insertedText.length
|
||||||
|
} else {
|
||||||
|
insertedText = '- '
|
||||||
|
selectionStart = start + 2
|
||||||
|
selectionEnd = start + insertedText.length
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'code':
|
||||||
|
default: {
|
||||||
|
if (hasSelection) {
|
||||||
|
if (selectedText.includes('\n')) {
|
||||||
|
insertedText = `\`\`\`\n${selectedText}\n\`\`\``
|
||||||
|
selectionStart = start + 4
|
||||||
|
selectionEnd = selectionStart + selectedText.length
|
||||||
|
} else {
|
||||||
|
insertedText = `\`${selectedText}\``
|
||||||
|
selectionStart = start + 1
|
||||||
|
selectionEnd = selectionStart + selectedText.length
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const text = ''
|
||||||
|
insertedText = `\`${text}\``
|
||||||
|
selectionStart = start + 1
|
||||||
|
selectionEnd = selectionStart + text.length
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextValue = value.slice(0, start) + insertedText + value.slice(end)
|
||||||
|
input.value = nextValue
|
||||||
|
input.setSelectionRange(selectionStart, selectionEnd)
|
||||||
|
input.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
|
||||||
|
return nextValue
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
# v26.3.1, build 235 (2026-03-xx xx:xx UTC)
|
# v26.3.1, build 235 (2026-03-xx xx:xx UTC)
|
||||||
|
- Added support for the new Qwen 3.5 model family.
|
||||||
|
- Added a reminder in chats and assistants that LLMs can make mistakes, helping you double-check important information more easily.
|
||||||
|
- Added the ability to format your user prompt in the chat using icons instead of typing Markdown directly.
|
||||||
- Improved the performance by caching the OS language detection and requesting the user language only once per app start.
|
- Improved the performance by caching the OS language detection and requesting the user language only once per app start.
|
||||||
- Improved the chat performance by reducing unnecessary UI updates, making chats smoother and more responsive, especially in longer conversations.
|
- Improved the chat performance by reducing unnecessary UI updates, making chats smoother and more responsive, especially in longer conversations.
|
||||||
- Improved the workspace loading experience: when opening the chat for the first time, your workspaces now appear faster and load step by step in the background, with placeholder rows so the app feels responsive right away.
|
- Improved the workspace loading experience: when opening the chat for the first time, your workspaces now appear faster and load step by step in the background, with placeholder rows so the app feels responsive right away.
|
||||||
|
- Improved the reliability of the global voice recording shortcut so it stays available more consistently.
|
||||||
- Improved the user-language logging by limiting language detection logs to a single entry per app start.
|
- Improved the user-language logging by limiting language detection logs to a single entry per app start.
|
||||||
- Improved the logbook readability by removing non-readable special characters from log entries.
|
- Improved the logbook readability by removing non-readable special characters from log entries.
|
||||||
- Improved the logbook reliability by significantly reducing duplicate log entries.
|
- Improved the logbook reliability by significantly reducing duplicate log entries.
|
||||||
- Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks Inga for reporting this issue and providing some context on how to reproduce it.
|
- Improved file attachments in chats: configuration and project files such as `Dockerfile`, `Caddyfile`, `Makefile`, or `Jenkinsfile` are now included more reliably when you send them to the AI.
|
||||||
|
- Improved the validation of additional API parameters in the advanced provider settings to help catch formatting mistakes earlier.
|
||||||
|
- Improved the app startup resilience by allowing AI Studio to continue without Qdrant if it fails to initialize.
|
||||||
|
- Fixed an issue where assistants hidden via configuration plugins still appear in "Send to ..." menus. Thanks, Gunnar, for reporting this issue.
|
||||||
|
- Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks, Inga, for reporting this issue and providing some context on how to reproduce it.
|
||||||
53
runtime/Cargo.lock
generated
53
runtime/Cargo.lock
generated
@ -354,9 +354,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "6.0.0"
|
version = "7.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
|
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
@ -2115,9 +2115,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ico"
|
name = "ico"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae"
|
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"png",
|
"png",
|
||||||
@ -2517,17 +2517,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "json-patch"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 1.0.63",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "json-patch"
|
name = "json-patch"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -5095,9 +5084,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.8.1"
|
version = "1.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bf327e247698d3f39af8aa99401c9708384290d1f5c544bf5d251d44c2fea22"
|
checksum = "3ae1f57c291a6ab8e1d2e6b8ad0a35ff769c9925deb8a89de85425ff08762d0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@ -5157,15 +5146,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "1.5.3"
|
version = "1.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0c6ec7a5c3296330c7818478948b422967ce4649094696c985f61d50076d29c"
|
checksum = "2db08694eec06f53625cfc6fff3a363e084e5e9a238166d2989996413c346453"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"json-patch 1.4.0",
|
"json-patch",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -5176,14 +5165,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "1.4.5"
|
version = "1.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93a9e3f5cebf779a63bf24903e714ec91196c307d8249a0008b882424328bcda"
|
checksum = "53438d78c4a037ffe5eafa19e447eea599bedfb10844cb08ec53c2471ac3ac3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"brotli",
|
"brotli",
|
||||||
"ico",
|
"ico",
|
||||||
"json-patch 2.0.0",
|
"json-patch",
|
||||||
"plist",
|
"plist",
|
||||||
"png",
|
"png",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -5202,9 +5191,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "1.4.6"
|
version = "1.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1d0e989f54fe06c5ef0875c5e19cf96453d099a0a774d5192ab47e80471cdab"
|
checksum = "233988ac08c1ed3fe794cd65528d48d8f7ed4ab3895ca64cdaa6ad4d00c45c0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -5230,9 +5219,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.14.5"
|
version = "0.14.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f33fda7d213e239077fad52e96c6b734cecedb30c2382118b64f94cb5103ff3a"
|
checksum = "8066855882f00172935e3fa7d945126580c34dcbabab43f5d4f0c2398a67d47b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
@ -5251,9 +5240,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "0.14.10"
|
version = "0.14.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18c447dcd9b0f09c7dc4b752cc33e72788805bfd761fbda5692d30c48289efec"
|
checksum = "ce361fec1e186705371f1c64ae9dd2a3a6768bc530d0a2d5e75a634bb416ad4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"gtk",
|
"gtk",
|
||||||
@ -5271,9 +5260,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "1.6.1"
|
version = "1.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83a0c939e88d82903a0a7dfb28388b12a3c03504d6bd6086550edaa3b6d8beaa"
|
checksum = "c357952645e679de02cd35007190fcbce869b93ffc61b029f33fe02648453774"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli",
|
"brotli",
|
||||||
"ctor",
|
"ctor",
|
||||||
@ -5282,7 +5271,7 @@ dependencies = [
|
|||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch 2.0.0",
|
"json-patch",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|||||||
@ -6,10 +6,10 @@ description = "MindWork AI Studio"
|
|||||||
authors = ["Thorsten Sommer"]
|
authors = ["Thorsten Sommer"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.5", features = [] }
|
tauri-build = { version = "1.5.6", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1.8", features = [ "http-all", "updater", "shell-sidecar", "shell-open", "dialog", "global-shortcut"] }
|
tauri = { version = "1.8.3", features = [ "http-all", "updater", "shell-sidecar", "shell-open", "dialog", "global-shortcut"] }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|||||||
@ -107,16 +107,16 @@ pub fn start_tauri() {
|
|||||||
DATA_DIRECTORY.set(data_path.to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the data directory.")).unwrap();
|
DATA_DIRECTORY.set(data_path.to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the data directory.")).unwrap();
|
||||||
CONFIG_DIRECTORY.set(app.path_resolver().app_config_dir().unwrap().to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the config directory.")).unwrap();
|
CONFIG_DIRECTORY.set(app.path_resolver().app_config_dir().unwrap().to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the config directory.")).unwrap();
|
||||||
|
|
||||||
cleanup_qdrant();
|
|
||||||
cleanup_dotnet_server();
|
|
||||||
|
|
||||||
if is_dev() {
|
if is_dev() {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
create_startup_env_file();
|
create_startup_env_file();
|
||||||
} else {
|
} else {
|
||||||
|
cleanup_dotnet_server();
|
||||||
start_dotnet_server();
|
start_dotnet_server();
|
||||||
}
|
}
|
||||||
start_qdrant_server();
|
|
||||||
|
cleanup_qdrant();
|
||||||
|
start_qdrant_server(app.path_resolver());
|
||||||
|
|
||||||
info!(Source = "Bootloader Tauri"; "Reconfigure the file logger to use the app data directory {data_path:?}");
|
info!(Source = "Bootloader Tauri"; "Reconfigure the file logger to use the app data directory {data_path:?}");
|
||||||
switch_to_file_logging(data_path).map_err(|e| error!("Failed to switch logging to file: {e}")).unwrap();
|
switch_to_file_logging(data_path).map_err(|e| error!("Failed to switch logging to file: {e}")).unwrap();
|
||||||
|
|||||||
@ -12,9 +12,10 @@ use rocket::serde::json::Json;
|
|||||||
use rocket::serde::Serialize;
|
use rocket::serde::Serialize;
|
||||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||||
use crate::api_token::{APIToken};
|
use crate::api_token::{APIToken};
|
||||||
use crate::environment::DATA_DIRECTORY;
|
use crate::environment::{is_dev, DATA_DIRECTORY};
|
||||||
use crate::certificate_factory::generate_certificate;
|
use crate::certificate_factory::generate_certificate;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use tauri::PathResolver;
|
||||||
use tempfile::{TempDir, Builder};
|
use tempfile::{TempDir, Builder};
|
||||||
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
use crate::stale_process_cleanup::{kill_stale_process, log_potential_stale_process};
|
||||||
use crate::sidecar_types::SidecarType;
|
use crate::sidecar_types::SidecarType;
|
||||||
@ -38,10 +39,24 @@ static API_TOKEN: Lazy<APIToken> = Lazy::new(|| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static TMPDIR: Lazy<Mutex<Option<TempDir>>> = Lazy::new(|| Mutex::new(None));
|
static TMPDIR: Lazy<Mutex<Option<TempDir>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
static QDRANT_STATUS: Lazy<Mutex<QdrantStatus>> = Lazy::new(|| Mutex::new(QdrantStatus::default()));
|
||||||
|
|
||||||
const PID_FILE_NAME: &str = "qdrant.pid";
|
const PID_FILE_NAME: &str = "qdrant.pid";
|
||||||
const SIDECAR_TYPE:SidecarType = SidecarType::Qdrant;
|
const SIDECAR_TYPE:SidecarType = SidecarType::Qdrant;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct QdrantStatus {
|
||||||
|
is_available: bool,
|
||||||
|
unavailable_reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qdrant_base_path() -> PathBuf {
|
||||||
|
let qdrant_directory = if is_dev() { "qdrant_test" } else { "qdrant" };
|
||||||
|
Path::new(DATA_DIRECTORY.get().unwrap())
|
||||||
|
.join("databases")
|
||||||
|
.join(qdrant_directory)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ProvideQdrantInfo {
|
pub struct ProvideQdrantInfo {
|
||||||
path: String,
|
path: String,
|
||||||
@ -49,34 +64,62 @@ pub struct ProvideQdrantInfo {
|
|||||||
port_grpc: u16,
|
port_grpc: u16,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
api_token: String,
|
api_token: String,
|
||||||
|
is_available: bool,
|
||||||
|
unavailable_reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/system/qdrant/info")]
|
#[get("/system/qdrant/info")]
|
||||||
pub fn qdrant_port(_token: APIToken) -> Json<ProvideQdrantInfo> {
|
pub fn qdrant_port(_token: APIToken) -> Json<ProvideQdrantInfo> {
|
||||||
|
let status = QDRANT_STATUS.lock().unwrap();
|
||||||
|
let is_available = status.is_available;
|
||||||
|
let unavailable_reason = status.unavailable_reason.clone();
|
||||||
|
|
||||||
Json(ProvideQdrantInfo {
|
Json(ProvideQdrantInfo {
|
||||||
path: Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").to_str().unwrap().to_string(),
|
path: if is_available {
|
||||||
port_http: *QDRANT_SERVER_PORT_HTTP,
|
qdrant_base_path().to_string_lossy().to_string()
|
||||||
port_grpc: *QDRANT_SERVER_PORT_GRPC,
|
} else {
|
||||||
fingerprint: CERTIFICATE_FINGERPRINT.get().expect("Certificate fingerprint not available").to_string(),
|
String::new()
|
||||||
api_token: API_TOKEN.to_hex_text().to_string(),
|
},
|
||||||
|
port_http: if is_available { *QDRANT_SERVER_PORT_HTTP } else { 0 },
|
||||||
|
port_grpc: if is_available { *QDRANT_SERVER_PORT_GRPC } else { 0 },
|
||||||
|
fingerprint: if is_available {
|
||||||
|
CERTIFICATE_FINGERPRINT.get().cloned().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
api_token: if is_available {
|
||||||
|
API_TOKEN.to_hex_text().to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
is_available,
|
||||||
|
unavailable_reason,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the Qdrant server in a separate process.
|
/// Starts the Qdrant server in a separate process.
|
||||||
pub fn start_qdrant_server(){
|
pub fn start_qdrant_server(path_resolver: PathResolver){
|
||||||
|
let path = qdrant_base_path();
|
||||||
let base_path = DATA_DIRECTORY.get().unwrap();
|
|
||||||
let path = Path::new(base_path).join("databases").join("qdrant");
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
if let Err(e) = fs::create_dir_all(&path){
|
if let Err(e) = fs::create_dir_all(&path){
|
||||||
error!(Source="Qdrant"; "The required directory to host the Qdrant database could not be created: {}", e.to_string());
|
error!(Source="Qdrant"; "The required directory to host the Qdrant database could not be created: {}", e);
|
||||||
|
set_qdrant_unavailable(format!("The Qdrant data directory could not be created: {e}"));
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let (cert_path, key_path) =create_temp_tls_files(&path).unwrap();
|
|
||||||
|
|
||||||
let storage_path = path.join("storage").to_str().unwrap().to_string();
|
let (cert_path, key_path) = match create_temp_tls_files(&path) {
|
||||||
let snapshot_path = path.join("snapshots").to_str().unwrap().to_string();
|
Ok(paths) => paths,
|
||||||
let init_path = path.join(".qdrant-initalized").to_str().unwrap().to_string();
|
Err(e) => {
|
||||||
|
error!(Source="Qdrant"; "TLS files for Qdrant could not be created: {e}");
|
||||||
|
set_qdrant_unavailable(format!("TLS files for Qdrant could not be created: {e}"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let storage_path = path.join("storage").to_string_lossy().to_string();
|
||||||
|
let snapshot_path = path.join("snapshots").to_string_lossy().to_string();
|
||||||
|
let init_path = path.join(".qdrant-initialized").to_string_lossy().to_string();
|
||||||
|
|
||||||
let qdrant_server_environment = HashMap::from_iter([
|
let qdrant_server_environment = HashMap::from_iter([
|
||||||
(String::from("QDRANT__SERVICE__HTTP_PORT"), QDRANT_SERVER_PORT_HTTP.to_string()),
|
(String::from("QDRANT__SERVICE__HTTP_PORT"), QDRANT_SERVER_PORT_HTTP.to_string()),
|
||||||
@ -84,22 +127,52 @@ pub fn start_qdrant_server(){
|
|||||||
(String::from("QDRANT_INIT_FILE_PATH"), init_path),
|
(String::from("QDRANT_INIT_FILE_PATH"), init_path),
|
||||||
(String::from("QDRANT__STORAGE__STORAGE_PATH"), storage_path),
|
(String::from("QDRANT__STORAGE__STORAGE_PATH"), storage_path),
|
||||||
(String::from("QDRANT__STORAGE__SNAPSHOTS_PATH"), snapshot_path),
|
(String::from("QDRANT__STORAGE__SNAPSHOTS_PATH"), snapshot_path),
|
||||||
(String::from("QDRANT__TLS__CERT"), cert_path.to_str().unwrap().to_string()),
|
(String::from("QDRANT__TLS__CERT"), cert_path.to_string_lossy().to_string()),
|
||||||
(String::from("QDRANT__TLS__KEY"), key_path.to_str().unwrap().to_string()),
|
(String::from("QDRANT__TLS__KEY"), key_path.to_string_lossy().to_string()),
|
||||||
(String::from("QDRANT__SERVICE__ENABLE_TLS"), "true".to_string()),
|
(String::from("QDRANT__SERVICE__ENABLE_TLS"), "true".to_string()),
|
||||||
(String::from("QDRANT__SERVICE__API_KEY"), API_TOKEN.to_hex_text().to_string()),
|
(String::from("QDRANT__SERVICE__API_KEY"), API_TOKEN.to_hex_text().to_string()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let server_spawn_clone = QDRANT_SERVER.clone();
|
let server_spawn_clone = QDRANT_SERVER.clone();
|
||||||
|
let qdrant_relative_source_path = "resources/databases/qdrant/config.yaml";
|
||||||
|
let qdrant_source_path = match path_resolver.resolve_resource(qdrant_relative_source_path) {
|
||||||
|
Some(path) => path,
|
||||||
|
None => {
|
||||||
|
let reason = format!("The Qdrant config resource '{qdrant_relative_source_path}' could not be resolved.");
|
||||||
|
error!(Source = "Qdrant"; "{reason} Starting the app without Qdrant.");
|
||||||
|
set_qdrant_unavailable(reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let qdrant_source_path_display = qdrant_source_path.to_string_lossy().to_string();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let (mut rx, child) = Command::new_sidecar("qdrant")
|
let sidecar = match Command::new_sidecar("qdrant") {
|
||||||
.expect("Failed to create sidecar for Qdrant")
|
Ok(sidecar) => sidecar,
|
||||||
.args(["--config-path", "resources/databases/qdrant/config.yaml"])
|
Err(e) => {
|
||||||
|
let reason = format!("Failed to create sidecar for Qdrant: {e}");
|
||||||
|
error!(Source = "Qdrant"; "{reason}");
|
||||||
|
set_qdrant_unavailable(reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut rx, child) = match sidecar
|
||||||
|
.args(["--config-path", qdrant_source_path_display.as_str()])
|
||||||
.envs(qdrant_server_environment)
|
.envs(qdrant_server_environment)
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to spawn Qdrant server process.");
|
{
|
||||||
|
Ok(process) => process,
|
||||||
|
Err(e) => {
|
||||||
|
let reason = format!("Failed to spawn Qdrant server process with config path '{}': {e}", qdrant_source_path_display);
|
||||||
|
error!(Source = "Qdrant"; "{reason}");
|
||||||
|
set_qdrant_unavailable(reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let server_pid = child.pid();
|
let server_pid = child.pid();
|
||||||
|
set_qdrant_available();
|
||||||
info!(Source = "Bootloader Qdrant"; "Qdrant server process started with PID={server_pid}.");
|
info!(Source = "Bootloader Qdrant"; "Qdrant server process started with PID={server_pid}.");
|
||||||
log_potential_stale_process(path.join(PID_FILE_NAME), server_pid, SIDECAR_TYPE);
|
log_potential_stale_process(path.join(PID_FILE_NAME), server_pid, SIDECAR_TYPE);
|
||||||
|
|
||||||
@ -137,7 +210,10 @@ pub fn stop_qdrant_server() {
|
|||||||
if let Some(server_process) = QDRANT_SERVER.lock().unwrap().take() {
|
if let Some(server_process) = QDRANT_SERVER.lock().unwrap().take() {
|
||||||
let server_kill_result = server_process.kill();
|
let server_kill_result = server_process.kill();
|
||||||
match server_kill_result {
|
match server_kill_result {
|
||||||
Ok(_) => warn!(Source = "Qdrant"; "Qdrant server process was stopped."),
|
Ok(_) => {
|
||||||
|
set_qdrant_unavailable("Qdrant server was stopped.".to_string());
|
||||||
|
warn!(Source = "Qdrant"; "Qdrant server process was stopped.")
|
||||||
|
},
|
||||||
Err(e) => error!(Source = "Qdrant"; "Failed to stop Qdrant server process: {e}."),
|
Err(e) => error!(Source = "Qdrant"; "Failed to stop Qdrant server process: {e}."),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -148,7 +224,7 @@ pub fn stop_qdrant_server() {
|
|||||||
cleanup_qdrant();
|
cleanup_qdrant();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create temporary directory with TLS relevant files
|
/// Create a temporary directory with TLS relevant files
|
||||||
pub fn create_temp_tls_files(path: &PathBuf) -> Result<(PathBuf, PathBuf), Box<dyn Error>> {
|
pub fn create_temp_tls_files(path: &PathBuf) -> Result<(PathBuf, PathBuf), Box<dyn Error>> {
|
||||||
let cert = generate_certificate();
|
let cert = generate_certificate();
|
||||||
|
|
||||||
@ -157,10 +233,10 @@ pub fn create_temp_tls_files(path: &PathBuf) -> Result<(PathBuf, PathBuf), Box<d
|
|||||||
let key_path = temp_dir.join("key.pem");
|
let key_path = temp_dir.join("key.pem");
|
||||||
|
|
||||||
let mut cert_file = File::create(&cert_path)?;
|
let mut cert_file = File::create(&cert_path)?;
|
||||||
cert_file.write_all(&*cert.certificate)?;
|
cert_file.write_all(&cert.certificate)?;
|
||||||
|
|
||||||
let mut key_file = File::create(&key_path)?;
|
let mut key_file = File::create(&key_path)?;
|
||||||
key_file.write_all(&*cert.private_key)?;
|
key_file.write_all(&cert.private_key)?;
|
||||||
|
|
||||||
CERTIFICATE_FINGERPRINT.set(cert.fingerprint).expect("Could not set the certificate fingerprint.");
|
CERTIFICATE_FINGERPRINT.set(cert.fingerprint).expect("Could not set the certificate fingerprint.");
|
||||||
|
|
||||||
@ -187,24 +263,35 @@ pub fn drop_tmpdir() {
|
|||||||
|
|
||||||
/// Remove old Pid files and kill the corresponding processes
|
/// Remove old Pid files and kill the corresponding processes
|
||||||
pub fn cleanup_qdrant() {
|
pub fn cleanup_qdrant() {
|
||||||
let pid_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant").join(PID_FILE_NAME);
|
let path = qdrant_base_path();
|
||||||
|
let pid_path = path.join(PID_FILE_NAME);
|
||||||
if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) {
|
if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) {
|
||||||
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
||||||
}
|
}
|
||||||
if let Err(e) = delete_old_certificates() {
|
if let Err(e) = delete_old_certificates(path) {
|
||||||
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
warn!(Source = "Qdrant"; "Error during the cleanup of Qdrant: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_old_certificates() -> Result<(), Box<dyn Error>> {
|
fn set_qdrant_available() {
|
||||||
let dir_path = Path::new(DATA_DIRECTORY.get().unwrap()).join("databases").join("qdrant");
|
let mut status = QDRANT_STATUS.lock().unwrap();
|
||||||
|
status.is_available = true;
|
||||||
|
status.unavailable_reason = None;
|
||||||
|
}
|
||||||
|
|
||||||
if !dir_path.exists() {
|
fn set_qdrant_unavailable(reason: String) {
|
||||||
|
let mut status = QDRANT_STATUS.lock().unwrap();
|
||||||
|
status.is_available = false;
|
||||||
|
status.unavailable_reason = Some(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_old_certificates(path: PathBuf) -> Result<(), Box<dyn Error>> {
|
||||||
|
if !path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in fs::read_dir(dir_path)? {
|
for entry in fs::read_dir(path)? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@
|
|||||||
"target/databases/qdrant/qdrant"
|
"target/databases/qdrant/qdrant"
|
||||||
],
|
],
|
||||||
"resources": [
|
"resources": [
|
||||||
"resources/*"
|
"resources/**"
|
||||||
],
|
],
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"exceptionDomain": "localhost"
|
"exceptionDomain": "localhost"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user