mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-02-12 21:21:37 +00:00
Merge branch 'main' into pr/594
# Conflicts: # app/MindWork AI Studio/Assistants/I18N/allTexts.lua # app/MindWork AI Studio/Components/AttachDocuments.razor.cs # app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua # app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
This commit is contained in:
commit
863844960a
@ -32,7 +32,7 @@ Since November 2024: Work on RAG (integration of your data and files) has begun.
|
||||
- [x] ~~App: Implement dialog for checking & handling [pandoc](https://pandoc.org/) installation ([PR #393](https://github.com/MindWorkAI/AI-Studio/pull/393), [PR #487](https://github.com/MindWorkAI/AI-Studio/pull/487))~~
|
||||
- [ ] App: Implement external embedding providers
|
||||
- [ ] App: Implement the process to vectorize one local file using embeddings
|
||||
- [ ] Runtime: Integration of the vector database [LanceDB](https://github.com/lancedb/lancedb)
|
||||
- [ ] Runtime: Integration of the vector database [Qdrant](https://github.com/qdrant/qdrant)
|
||||
- [ ] App: Implement the continuous process of vectorizing data
|
||||
- [x] ~~App: Define a common retrieval context interface for the integration of RAG processes in chats (PR [#281](https://github.com/MindWorkAI/AI-Studio/pull/281), [#284](https://github.com/MindWorkAI/AI-Studio/pull/284), [#286](https://github.com/MindWorkAI/AI-Studio/pull/286), [#287](https://github.com/MindWorkAI/AI-Studio/pull/287))~~
|
||||
- [x] ~~App: Define a common augmentation interface for the integration of RAG processes in chats (PR [#288](https://github.com/MindWorkAI/AI-Studio/pull/288), [#289](https://github.com/MindWorkAI/AI-Studio/pull/289))~~
|
||||
@ -79,6 +79,7 @@ Since March 2025: We have started developing the plugin system. There will be la
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
- v0.9.55: Added support for newer models like Mistral 3 & GPT 5.2, OpenRouter as LLM and embedding provider, and the possibility to use file attachments in chats.
|
||||
- v0.9.51: Added support for [Perplexity](https://www.perplexity.ai/); citations added so that LLMs can provide source references (e.g., some OpenAI models, Perplexity); added support for OpenAI's Responses API so that all text LLMs from OpenAI now work in MindWork AI Studio, including Deep Research models; web searches are now possible (some OpenAI models, Perplexity).
|
||||
- v0.9.50: Added support for self-hosted LLMs using [vLLM](https://blog.vllm.ai/2023/06/20/vllm.html).
|
||||
- v0.9.46: Released our plugin system, a German language plugin, early support for enterprise environments, and configuration plugins. Additionally, we added the Pandoc integration for future data processing and file generation.
|
||||
@ -114,6 +115,7 @@ MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provi
|
||||
- [xAI](https://x.ai/) (Grok)
|
||||
- [DeepSeek](https://www.deepseek.com/en)
|
||||
- [Alibaba Cloud](https://www.alibabacloud.com) (Qwen)
|
||||
- [OpenRouter](https://openrouter.ai/)
|
||||
- [Hugging Face](https://huggingface.co/) using their [inference providers](https://huggingface.co/docs/inference-providers/index) such as Cerebras, Nebius, Sambanova, Novita, Hyperbolic, Together AI, Fireworks, Hugging Face
|
||||
- Self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), and [vLLM](https://github.com/vllm-project/vllm)
|
||||
- [Groq](https://groq.com/)
|
||||
|
||||
@ -56,10 +56,18 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
[
|
||||
new ButtonData
|
||||
{
|
||||
#if DEBUG
|
||||
Text = T("Write Lua code to language plugin file"),
|
||||
#else
|
||||
Text = T("Copy Lua code to clipboard"),
|
||||
#endif
|
||||
Icon = Icons.Material.Filled.Extension,
|
||||
Color = Color.Default,
|
||||
#if DEBUG
|
||||
AsyncAction = async () => await this.WriteToPluginFile(),
|
||||
#else
|
||||
AsyncAction = async () => await this.RustService.CopyText2Clipboard(this.Snackbar, this.finalLuaCode.ToString()),
|
||||
#endif
|
||||
DisabledActionParam = () => this.finalLuaCode.Length == 0,
|
||||
},
|
||||
];
|
||||
@ -368,10 +376,71 @@ public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
{
|
||||
this.finalLuaCode.Clear();
|
||||
LuaTable.Create(ref this.finalLuaCode, "UI_TEXT_CONTENT", this.localizedContent, commentContent, this.cancellationTokenSource!.Token);
|
||||
|
||||
|
||||
// Next, we must remove the `root::` prefix from the keys:
|
||||
this.finalLuaCode.Replace("""UI_TEXT_CONTENT["root::""", """
|
||||
UI_TEXT_CONTENT["
|
||||
""");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private async Task WriteToPluginFile()
|
||||
{
|
||||
if (this.selectedLanguagePlugin is null)
|
||||
{
|
||||
this.Snackbar.Add(T("No language plugin selected."), Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.finalLuaCode.Length == 0)
|
||||
{
|
||||
this.Snackbar.Add(T("No Lua code generated yet."), Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Determine the plugin file path based on the selected language plugin:
|
||||
var pluginDirectory = Path.Join(Environment.CurrentDirectory, "Plugins", "languages");
|
||||
var pluginId = this.selectedLanguagePluginId.ToString();
|
||||
var ietfTag = this.selectedLanguagePlugin.IETFTag.ToLowerInvariant();
|
||||
var pluginFolderName = $"{ietfTag}-{pluginId}";
|
||||
var pluginFilePath = Path.Join(pluginDirectory, pluginFolderName, "plugin.lua");
|
||||
|
||||
if (!File.Exists(pluginFilePath))
|
||||
{
|
||||
this.Logger.LogError("Plugin file not found: {PluginFilePath}.", pluginFilePath);
|
||||
this.Snackbar.Add(T("Plugin file not found."), Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the existing plugin file:
|
||||
var existingContent = await File.ReadAllTextAsync(pluginFilePath);
|
||||
|
||||
// Find the position of "UI_TEXT_CONTENT = {}":
|
||||
const string MARKER = "UI_TEXT_CONTENT = {}";
|
||||
var markerIndex = existingContent.IndexOf(MARKER, StringComparison.Ordinal);
|
||||
|
||||
if (markerIndex == -1)
|
||||
{
|
||||
this.Logger.LogError("Could not find 'UI_TEXT_CONTENT = {{}}' marker in plugin file: {PluginFilePath}", pluginFilePath);
|
||||
this.Snackbar.Add(T("Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file."), Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep everything before the marker and replace everything from the marker onwards:
|
||||
var metadataSection = existingContent[..markerIndex];
|
||||
var newContent = metadataSection + this.finalLuaCode;
|
||||
|
||||
// Write the updated content back to the file:
|
||||
await File.WriteAllTextAsync(pluginFilePath, newContent);
|
||||
this.Snackbar.Add(T("Successfully updated plugin file."), Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Error writing to plugin file.");
|
||||
this.Snackbar.Add(T("Error writing to plugin file."), Severity.Error);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -1042,21 +1042,36 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1214535771"] = "Rem
|
||||
-- Added Content ({0} entries)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1258080997"] = "Added Content ({0} entries)"
|
||||
|
||||
-- No Lua code generated yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1365137848"] = "No Lua code generated yet."
|
||||
|
||||
-- Localized Content ({0} entries of {1})
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1492071634"] = "Localized Content ({0} entries of {1})"
|
||||
|
||||
-- Select the language plugin used for comparision.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1523568309"] = "Select the language plugin used for comparision."
|
||||
|
||||
-- Successfully updated plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1524590750"] = "Successfully updated plugin file."
|
||||
|
||||
-- Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1893011391"] = "Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin."
|
||||
|
||||
-- No language plugin selected.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237325294"] = "No language plugin selected."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237828418"] = "Target language"
|
||||
|
||||
-- Write Lua code to language plugin file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T253827221"] = "Write Lua code to language plugin file"
|
||||
|
||||
-- Language plugin used for comparision
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T263317578"] = "Language plugin used for comparision"
|
||||
|
||||
-- Plugin file not found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T2938065913"] = "Plugin file not found."
|
||||
|
||||
-- Localize AI Studio & generate the Lua code
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T3055634395"] = "Localize AI Studio & generate the Lua code"
|
||||
|
||||
@ -1087,6 +1102,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T453060723"] = "The
|
||||
-- The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T458999393"] = "The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language."
|
||||
|
||||
-- Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T628596031"] = "Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file."
|
||||
|
||||
-- Please provide a custom language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T656744944"] = "Please provide a custom language."
|
||||
|
||||
@ -1096,6 +1114,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T851515643"] = "Plea
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T897888480"] = "Localization"
|
||||
|
||||
-- Error writing to plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T948564909"] = "Error writing to plugin file."
|
||||
|
||||
-- Your icon source
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1302165948"] = "Your icon source"
|
||||
|
||||
@ -1492,6 +1513,12 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee
|
||||
-- Export Chat to Microsoft Word
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
|
||||
|
||||
-- The local image file is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T3219823625"] = "The local image file is too large (>10 MB). Skipping the image."
|
||||
|
||||
-- The image at the URL is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T349928509"] = "The image at the URL is too large (>10 MB). Skipping the image."
|
||||
|
||||
-- Open Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings"
|
||||
|
||||
@ -1516,15 +1543,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Click to attach files
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3521845090"] = "Click to attach files"
|
||||
|
||||
-- Clear file list
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Clear file list"
|
||||
|
||||
-- Add file
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4014053962"] = "Add file"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- Select a file to attach
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Select a file to attach"
|
||||
|
||||
@ -1828,21 +1855,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T900237532"] = "Provid
|
||||
-- Failed to load file content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T1989554334"] = "Failed to load file content"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos are not supported yet"
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Use file content as input
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3499386973"] = "Use file content as input"
|
||||
|
||||
-- Select file to read its content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T354817589"] = "Select file to read its content"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used."
|
||||
|
||||
@ -3421,24 +3439,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Canc
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Embeddings"
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1746160064"] = "Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon."
|
||||
|
||||
-- There aren't any file attachments available right now.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2111340711"] = "There aren't any file attachments available right now."
|
||||
|
||||
-- The file was deleted, renamed, or moved.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T3083729256"] = "The file was deleted, renamed, or moved."
|
||||
|
||||
-- Your attached file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T3154198222"] = "Your attached file."
|
||||
|
||||
-- Your attached files
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T3909191077"] = "Your attached files"
|
||||
|
||||
-- Remove this attachment.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T3933470258"] = "Remove this attachment."
|
||||
|
||||
-- There is no social event
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "There is no social event"
|
||||
|
||||
@ -4918,9 +4918,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1702902297"] = "Introduction"
|
||||
-- Vision
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2183503084"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
||||
|
||||
-- Let's get started
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Let's get started"
|
||||
|
||||
@ -4945,6 +4942,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective"
|
||||
-- Flexibility
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3892227145"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
||||
|
||||
-- Privacy
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy"
|
||||
|
||||
@ -5902,6 +5902,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- Executable Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
|
||||
|
||||
-- All Audio Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
|
||||
|
||||
-- All Video Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files"
|
||||
|
||||
@ -6031,6 +6034,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T878007824"]
|
||||
-- Please enter the secret necessary for authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication."
|
||||
|
||||
-- Unsupported image format
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Unsupported image format"
|
||||
|
||||
-- File has no extension
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "File has no extension"
|
||||
|
||||
-- Audio files are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2928927510"] = "Videos are not supported yet"
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- The hostname is not a valid HTTP(S) URL.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T1013354736"] = "The hostname is not a valid HTTP(S) URL."
|
||||
|
||||
|
||||
@ -108,9 +108,21 @@
|
||||
break;
|
||||
|
||||
case ContentType.IMAGE:
|
||||
if (this.Content is ContentImage { SourceType: ContentImageSource.URL or ContentImageSource.LOCAL_PATH } imageContent)
|
||||
if (this.Content is ContentImage imageContent)
|
||||
{
|
||||
<MudImage Src="@imageContent.Source"/>
|
||||
var imageSrc = imageContent.SourceType switch
|
||||
{
|
||||
ContentImageSource.BASE64 => ImageHelpers.ToDataUrl(imageContent.Source),
|
||||
ContentImageSource.URL => imageContent.Source,
|
||||
ContentImageSource.LOCAL_PATH => imageContent.Source,
|
||||
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imageSrc))
|
||||
{
|
||||
<MudImage Src="@imageSrc" />
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
@ -50,6 +51,23 @@ public sealed class ContentImage : IContent, IImageSource
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ContentImage from a local file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The path to the image file.</param>
|
||||
/// <returns>A new ContentImage instance if the file is valid, null otherwise.</returns>
|
||||
public static async Task<ContentImage?> CreateFromFileAsync(string filePath)
|
||||
{
|
||||
if (!await FileExtensionValidation.IsImageExtensionValidWithNotifyAsync(filePath))
|
||||
return null;
|
||||
|
||||
return new ContentImage
|
||||
{
|
||||
SourceType = ContentImageSource.LOCAL_PATH,
|
||||
Source = filePath,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the image source.
|
||||
/// </summary>
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
public static class IImageSourceExtensions
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IImageSourceExtensions).Namespace, nameof(IImageSourceExtensions));
|
||||
|
||||
/// <summary>
|
||||
/// Read the image content as a base64 string.
|
||||
/// </summary>
|
||||
@ -33,8 +37,11 @@ public static class IImageSourceExtensions
|
||||
// Read the length of the content:
|
||||
var lengthBytes = response.Content.Headers.ContentLength;
|
||||
if(lengthBytes > 10_000_000)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The image at the URL is too large (>10 MB). Skipping the image.")));
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync(token);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
@ -48,8 +55,11 @@ public static class IImageSourceExtensions
|
||||
// Read the content length:
|
||||
var length = new FileInfo(image.Source).Length;
|
||||
if(length > 10_000_000)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.ImageNotSupported, TB("The local image file is too large (>10 MB). Skipping the image.")));
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
var bytes = await File.ReadAllBytesAsync(image.Source, token);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@ -42,7 +43,10 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
||||
|
||||
private static readonly string DROP_FILES_HERE_TEXT = TB("Drop files here to attach them.");
|
||||
@ -92,12 +96,26 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop dropped event.", this.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Ensure that Pandoc is installed and ready:
|
||||
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
|
||||
showSuccessMessage: false,
|
||||
showDialog: true);
|
||||
|
||||
// If Pandoc is not available (user cancelled installation), abort file drop:
|
||||
if (!pandocState.IsAvailable)
|
||||
{
|
||||
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file drop.");
|
||||
this.ClearDragClass();
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if(!await this.IsFileExtensionValid(path))
|
||||
if(!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(path))
|
||||
continue;
|
||||
|
||||
|
||||
this.DocumentPaths.Add(path);
|
||||
}
|
||||
|
||||
@ -118,17 +136,33 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
private async Task AddFilesManually()
|
||||
{
|
||||
var selectedFile = await this.RustService.SelectFile(T("Select a file to attach"));
|
||||
if (selectedFile.UserCancelled)
|
||||
// Ensure that Pandoc is installed and ready:
|
||||
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
|
||||
showSuccessMessage: false,
|
||||
showDialog: true);
|
||||
|
||||
// If Pandoc is not available (user cancelled installation), abort file selection:
|
||||
if (!pandocState.IsAvailable)
|
||||
{
|
||||
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectFiles = await this.RustService.SelectFiles(T("Select a file to attach"));
|
||||
if (selectFiles.UserCancelled)
|
||||
return;
|
||||
|
||||
if (!File.Exists(selectedFile.SelectedFilePath))
|
||||
return;
|
||||
foreach (var selectedFilePath in selectFiles.SelectedFilePaths)
|
||||
{
|
||||
if (!File.Exists(selectedFilePath))
|
||||
continue;
|
||||
|
||||
if (!await this.IsFileExtensionValid(selectedFile.SelectedFilePath))
|
||||
return;
|
||||
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(selectedFilePath))
|
||||
return;
|
||||
|
||||
this.DocumentPaths.Add(selectedFile.SelectedFilePath);
|
||||
this.DocumentPaths.Add(selectedFilePath);
|
||||
}
|
||||
|
||||
await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths);
|
||||
await this.OnChange(this.DocumentPaths);
|
||||
}
|
||||
@ -138,30 +172,6 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
this.DocumentPaths = await ReviewAttachmentsDialog.OpenDialogAsync(this.DialogService, this.DocumentPaths);
|
||||
}
|
||||
|
||||
private async Task<bool> IsFileExtensionValid(string selectedFile)
|
||||
{
|
||||
var ext = Path.GetExtension(selectedFile).TrimStart('.');
|
||||
if (Array.Exists(FileTypeFilter.Executables.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.AppBlocking, this.T("Executables are not allowed")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllImages.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.ImageNotSupported, this.T("Images are not supported yet")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllVideos.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.FeaturedVideo, this.T("Videos are not supported yet")));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ClearAllFiles()
|
||||
{
|
||||
this.DocumentPaths.Clear();
|
||||
|
||||
@ -83,11 +83,8 @@
|
||||
|
||||
<ChatTemplateSelection CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
|
||||
|
||||
@if (this.isPandocAvailable)
|
||||
{
|
||||
<AttachDocuments Name="File Attachments" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true"/>
|
||||
}
|
||||
|
||||
<AttachDocuments Name="File Attachments" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
|
||||
{
|
||||
<MudTooltip Text="@T("Delete this chat & start a new one.")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
|
||||
@ -3,7 +3,6 @@ using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
@ -38,9 +37,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private PandocAvailabilityService PandocAvailabilityService { get; init; } = null!;
|
||||
|
||||
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
@ -62,7 +58,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
private Guid currentWorkspaceId = Guid.Empty;
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private HashSet<string> chatDocumentPaths = [];
|
||||
private bool isPandocAvailable;
|
||||
|
||||
// Unfortunately, we need the input field reference to blur the focus away. Without
|
||||
// this, we cannot clear the input field.
|
||||
@ -204,9 +199,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
|
||||
// Check if Pandoc is available (no dialog or messages):
|
||||
this.isPandocAvailable = await this.PandocAvailabilityService.IsAvailableAsync();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
using AIStudio.Tools.Validation;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@ -30,6 +30,18 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
|
||||
private async Task SelectFile()
|
||||
{
|
||||
// Ensure that Pandoc is installed and ready:
|
||||
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
|
||||
showSuccessMessage: false,
|
||||
showDialog: true);
|
||||
|
||||
// Check if Pandoc is available after the check / installation:
|
||||
if (!pandocState.IsAvailable)
|
||||
{
|
||||
this.Logger.LogWarning("The user cancelled the Pandoc installation or Pandoc is not available. Aborting file selection.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedFile = await this.RustService.SelectFile(T("Select file to read its content"));
|
||||
if (selectedFile.UserCancelled)
|
||||
{
|
||||
@ -43,33 +55,12 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(selectedFile.SelectedFilePath).TrimStart('.');
|
||||
if (Array.Exists(FileTypeFilter.Executables.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(selectedFile.SelectedFilePath))
|
||||
{
|
||||
this.Logger.LogWarning("User attempted to load executable file: {FilePath} with extension: {Extension}", selectedFile.SelectedFilePath, ext);
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.AppBlocking, T("Executables are not allowed")));
|
||||
this.Logger.LogWarning("User attempted to load unsupported file: {FilePath}", selectedFile.SelectedFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllImages.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
this.Logger.LogWarning("User attempted to load image file: {FilePath} with extension: {Extension}", selectedFile.SelectedFilePath, ext);
|
||||
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.ImageNotSupported, T("Images are not supported yet")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllVideos.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
this.Logger.LogWarning("User attempted to load video file: {FilePath} with extension: {Extension}", selectedFile.SelectedFilePath, ext);
|
||||
await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.FeaturedVideo, this.T("Videos are not supported yet")));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that Pandoc is installed and ready:
|
||||
await this.PandocAvailabilityService.EnsureAvailabilityAsync(
|
||||
showSuccessMessage: false,
|
||||
showDialog: true);
|
||||
|
||||
try
|
||||
{
|
||||
var fileContent = await UserFile.LoadFileData(selectedFile.SelectedFilePath, this.RustService, this.DialogService);
|
||||
|
||||
@ -47,13 +47,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.2.5" />
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.11" />
|
||||
<PackageReference Include="MudBlazor" Version="8.12.0" />
|
||||
<PackageReference Include="MudBlazor" Version="8.15.0" />
|
||||
<PackageReference Include="MudBlazor.Markdown" Version="8.11.0" />
|
||||
<PackageReference Include="ReverseMarkdown" Version="4.7.1" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.4.2" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -61,12 +61,6 @@
|
||||
<ProjectReference Include="..\SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Dialogs\PandocDocumentCheckDialog.razor.cs">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Read the meta data file -->
|
||||
<Target Name="ReadMetaData" BeforeTargets="BeforeBuild">
|
||||
<Error Text="The ../../metadata.txt file was not found!" Condition="!Exists('../../metadata.txt')" />
|
||||
|
||||
@ -31,7 +31,7 @@ public partial class Home : MSGComponentBase
|
||||
{
|
||||
this.itemsAdvantages = [
|
||||
new(this.T("Free of charge"), this.T("The app is free to use, both for personal and commercial purposes.")),
|
||||
new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")),
|
||||
new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")),
|
||||
new(this.T("Assistants"), this.T("You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.")),
|
||||
new(this.T("Unrestricted usage"), this.T("Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.")),
|
||||
new(this.T("Cost-effective"), this.T("You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.")),
|
||||
|
||||
@ -1044,21 +1044,36 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1214535771"] = "Ent
|
||||
-- Added Content ({0} entries)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1258080997"] = "Hinzugefügte Inhalte ({0} Einträge)"
|
||||
|
||||
-- No Lua code generated yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1365137848"] = "Es wurde kein Lua-Code generiert."
|
||||
|
||||
-- Localized Content ({0} entries of {1})
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1492071634"] = "Lokalisierte Inhalte ({0} von {1} Einträgen)"
|
||||
|
||||
-- Select the language plugin used for comparision.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1523568309"] = "Wählen Sie das Sprach-Plugin für den Vergleich aus."
|
||||
|
||||
-- Successfully updated plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1524590750"] = "Plugin-Datei erfolgreich aktualisiert."
|
||||
|
||||
-- Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1893011391"] = "Das Sprach-Plugin für den Vergleich konnte nicht geladen werden ({0}). Bitte wählen Sie ein gültiges, geladenes und laufendes Sprach-Plugin."
|
||||
|
||||
-- No language plugin selected.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237325294"] = "Kein Sprach-Plugin ausgewählt."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237828418"] = "Zielsprache"
|
||||
|
||||
-- Write Lua code to language plugin file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T253827221"] = "Lua-Code für Sprach-Plugin-Datei schreiben"
|
||||
|
||||
-- Language plugin used for comparision
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T263317578"] = "Sprach-Plugin für den Vergleich"
|
||||
|
||||
-- Plugin file not found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T2938065913"] = "Plugin-Datei nicht gefunden."
|
||||
|
||||
-- Localize AI Studio & generate the Lua code
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T3055634395"] = "Lokalisiere AI Studio & generiere den Lua-Code"
|
||||
|
||||
@ -1089,6 +1104,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T453060723"] = "Die
|
||||
-- The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T458999393"] = "Das ausgewählte Sprach-Plugin für den Vergleich verwendet das IETF-Tag „{0}“, das nicht mit der ausgewählten Zielsprache „{1}“ übereinstimmt. Bitte wähle ein gültiges, geladenes und laufendes Sprach-Plugin aus, das mit der Zielsprache übereinstimmt."
|
||||
|
||||
-- Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T628596031"] = "Konnte den 'UI_TEXT_CONTENT = {}'-Marker in der Plugin-Datei nicht finden."
|
||||
|
||||
-- Please provide a custom language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T656744944"] = "Bitte geben Sie eine benutzerdefinierte Sprache an."
|
||||
|
||||
@ -1098,6 +1116,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T851515643"] = "Bitt
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T897888480"] = "Lokalisierung"
|
||||
|
||||
-- Error writing to plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T948564909"] = "Fehler beim Schreiben in die Plugin-Datei."
|
||||
|
||||
-- Your icon source
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1302165948"] = "Ihre Icons-Quelle"
|
||||
|
||||
@ -1494,24 +1515,21 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "Nein, b
|
||||
-- Export Chat to Microsoft Word
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in Microsoft Word exportieren"
|
||||
|
||||
-- The local image file is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T3219823625"] = "Die lokale Bilddatei ist zu groß (>10 MB). Das Bild wird übersprungen."
|
||||
|
||||
-- The image at the URL is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T349928509"] = "Das Bild unter der URL ist zu groß (>10 MB). Das Bild wird übersprungen."
|
||||
|
||||
-- Open Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Einstellungen öffnen"
|
||||
|
||||
-- You can edit your files here
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2038346788"] = "Sie können Ihre Dateien hier bearbeiten"
|
||||
|
||||
-- Drag and drop files into the marked area or click here to attach documents:
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T230755331"] = "Ziehen Sie Dateien in den markierten Bereich oder klicken Sie hier, um Dokumente anzuhängen:"
|
||||
|
||||
-- Document Preview
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T285154968"] = "Dokumentenvorschau"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos werden noch nicht unterstützt."
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Bilder werden noch nicht unterstützt."
|
||||
|
||||
-- Click to attach files
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3521845090"] = "Klicken, um Dateien anzuhängen"
|
||||
|
||||
@ -1521,15 +1539,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Dateili
|
||||
-- Add file
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4014053962"] = "Datei hinzufügen"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Ausführbare Dateien sind nicht erlaubt"
|
||||
|
||||
-- Select a file to attach
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Datei zum Anhängen auswählen"
|
||||
|
||||
-- Click to see your attached files
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T743568733"] = "Klicken Sie, um Ihre angehängten Dateien anzuzeigen"
|
||||
|
||||
-- Changelog
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Änderungsprotokoll"
|
||||
|
||||
@ -1830,21 +1842,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T900237532"] = "Anbiet
|
||||
-- Failed to load file content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T1989554334"] = "Laden des Dateiinhalts fehlgeschlagen"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos werden noch nicht unterstützt."
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Bilder werden derzeit nicht unterstützt"
|
||||
|
||||
-- Use file content as input
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3499386973"] = "Dokumenteninhalt als Eingabe verwenden"
|
||||
|
||||
-- Select file to read its content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T354817589"] = "Datei auswählen, um den Inhalt zu lesen"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T4167762413"] = "Ausführbare Dateien sind nicht erlaubt"
|
||||
|
||||
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "Der Inhalt wird mithilfe eines LLM-Agents bereinigt: Der Hauptinhalt wird extrahiert, Werbung und andere irrelevante Elemente werden nach Möglichkeit entfernt. Relative Links werden nach Möglichkeit in absolute Links umgewandelt, damit sie verwendet werden können."
|
||||
|
||||
@ -3423,18 +3426,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Abbr
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Einbettungen"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1469573738"] = "Löschen"
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1746160064"] = "Hier sehen Sie alle angehängten Dateien. Dateien, die nicht mehr gefunden werden können (gelöscht, umbenannt oder verschoben), sind mit einem Warnsymbol und einem durchgestrichenen Namen gekennzeichnet. Sie können jede Anlage mit dem Papierkorb-Symbol entfernen."
|
||||
|
||||
-- The file was deleted, renamed, or moved
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2203427286"] = "Die Datei wurde gelöscht, umbenannt oder verschoben"
|
||||
|
||||
-- Your attached file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T351780500"] = "Ihre angehängte Datei"
|
||||
|
||||
-- There is no social event
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "Es gibt keine gesellschaftliche Veranstaltung."
|
||||
|
||||
@ -4914,9 +4905,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1702902297"] = "Einführung"
|
||||
-- Vision
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2183503084"] = "Sie sind an keinen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face und selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio, Groq oder Fireworks. Für Wissenschaftler und Mitarbeiter von Forschungseinrichtungen unterstützen wir auch die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Anmeldungen wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen und viele internationale Universitäten verfügbar."
|
||||
|
||||
-- Let's get started
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Los geht's"
|
||||
|
||||
@ -4941,6 +4929,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Kosteneffizient"
|
||||
-- Flexibility
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibilität"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3892227145"] = "Sie sind an keinen einzelnen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu ihren Bedürfnissen passt. Derzeit unterstützen wir OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face und selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio, Groq oder Fireworks. Für Wissenschaftler und Mitarbeiter von Forschungseinrichtungen unterstützen wir auch die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Anmeldungen wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen und viele internationale Universitäten verfügbar."
|
||||
|
||||
-- Privacy
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Datenschutz"
|
||||
|
||||
@ -5898,6 +5889,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- Executable Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Ausführbare Dateien"
|
||||
|
||||
-- All Audio Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "Alle Audiodateien"
|
||||
|
||||
-- All Video Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "Alle Videodateien"
|
||||
|
||||
@ -6027,6 +6021,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T878007824"]
|
||||
-- Please enter the secret necessary for authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Bitte geben Sie das für die Authentifizierung benötigte Geheimnis ein."
|
||||
|
||||
-- Unsupported image format
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Nicht unterstütztes Bildformat"
|
||||
|
||||
-- File has no extension
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "Datei hat keine Erweiterung"
|
||||
|
||||
-- Audio files are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio-Dateien werden noch nicht unterstützt."
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2928927510"] = "Videos werden noch nicht unterstützt."
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T298062956"] = "Bilder werden derzeit nicht unterstützt."
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T4167762413"] = "Ausführbare Dateien sind nicht erlaubt"
|
||||
|
||||
-- The hostname is not a valid HTTP(S) URL.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T1013354736"] = "Der Hostname ist keine gültige HTTP(S)-URL."
|
||||
|
||||
@ -6064,4 +6076,4 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] =
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unbenannter Arbeitsbereich"
|
||||
|
||||
-- Delete Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Chat löschen"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Chat löschen"
|
||||
|
||||
@ -1044,21 +1044,36 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1214535771"] = "Rem
|
||||
-- Added Content ({0} entries)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1258080997"] = "Added Content ({0} entries)"
|
||||
|
||||
-- No Lua code generated yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1365137848"] = "No Lua code generated yet."
|
||||
|
||||
-- Localized Content ({0} entries of {1})
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1492071634"] = "Localized Content ({0} entries of {1})"
|
||||
|
||||
-- Select the language plugin used for comparision.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1523568309"] = "Select the language plugin used for comparision."
|
||||
|
||||
-- Successfully updated plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1524590750"] = "Successfully updated plugin file."
|
||||
|
||||
-- Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T1893011391"] = "Was not able to load the language plugin for comparison ({0}). Please select a valid, loaded & running language plugin."
|
||||
|
||||
-- No language plugin selected.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237325294"] = "No language plugin selected."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T237828418"] = "Target language"
|
||||
|
||||
-- Write Lua code to language plugin file
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T253827221"] = "Write Lua code to language plugin file"
|
||||
|
||||
-- Language plugin used for comparision
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T263317578"] = "Language plugin used for comparision"
|
||||
|
||||
-- Plugin file not found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T2938065913"] = "Plugin file not found."
|
||||
|
||||
-- Localize AI Studio & generate the Lua code
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T3055634395"] = "Localize AI Studio & generate the Lua code"
|
||||
|
||||
@ -1089,6 +1104,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T453060723"] = "The
|
||||
-- The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T458999393"] = "The selected language plugin for comparison uses the IETF tag '{0}' which does not match the selected target language '{1}'. Please select a valid, loaded & running language plugin which matches the target language."
|
||||
|
||||
-- Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T628596031"] = "Could not find 'UI_TEXT_CONTENT = {}' marker in plugin file."
|
||||
|
||||
-- Please provide a custom language.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T656744944"] = "Please provide a custom language."
|
||||
|
||||
@ -1098,6 +1116,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T851515643"] = "Plea
|
||||
-- Localization
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T897888480"] = "Localization"
|
||||
|
||||
-- Error writing to plugin file.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::I18N::ASSISTANTI18N::T948564909"] = "Error writing to plugin file."
|
||||
|
||||
-- Your icon source
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1302165948"] = "Your icon source"
|
||||
|
||||
@ -1494,24 +1515,21 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, kee
|
||||
-- Export Chat to Microsoft Word
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export Chat to Microsoft Word"
|
||||
|
||||
-- The local image file is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T3219823625"] = "The local image file is too large (>10 MB). Skipping the image."
|
||||
|
||||
-- The image at the URL is too large (>10 MB). Skipping the image.
|
||||
UI_TEXT_CONTENT["AISTUDIO::CHAT::IIMAGESOURCEEXTENSIONS::T349928509"] = "The image at the URL is too large (>10 MB). Skipping the image."
|
||||
|
||||
-- Open Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings"
|
||||
|
||||
-- You can edit your files here
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2038346788"] = "You can edit your files here"
|
||||
|
||||
-- Drag and drop files into the marked area or click here to attach documents:
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T230755331"] = "Drag and drop files into the marked area or click here to attach documents:"
|
||||
|
||||
-- Document Preview
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T285154968"] = "Document Preview"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos are not supported yet"
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Click to attach files
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3521845090"] = "Click to attach files"
|
||||
|
||||
@ -1521,15 +1539,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Clear f
|
||||
-- Add file
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4014053962"] = "Add file"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- Select a file to attach
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Select a file to attach"
|
||||
|
||||
-- Click to see your attached files
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T743568733"] = "Click to see your attached files"
|
||||
|
||||
-- Changelog
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Changelog"
|
||||
|
||||
@ -1830,21 +1842,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T900237532"] = "Provid
|
||||
-- Failed to load file content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T1989554334"] = "Failed to load file content"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos are not supported yet"
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Use file content as input
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T3499386973"] = "Use file content as input"
|
||||
|
||||
-- Select file to read its content
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T354817589"] = "Select file to read its content"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used."
|
||||
|
||||
@ -3423,18 +3426,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T900713019"] = "Canc
|
||||
-- Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::RETRIEVALPROCESSDIALOG::T951463987"] = "Embeddings"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1469573738"] = "Delete"
|
||||
|
||||
-- Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T1746160064"] = "Here you can see all attached files. Files that can no longer be found (deleted, renamed, or moved) are marked with a warning icon and a strikethrough name. You can remove any attachment using the trash can icon."
|
||||
|
||||
-- The file was deleted, renamed, or moved
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T2203427286"] = "The file was deleted, renamed, or moved"
|
||||
|
||||
-- Your attached file
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::REVIEWATTACHMENTSDIALOG::T351780500"] = "Your attached file"
|
||||
|
||||
-- There is no social event
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "There is no social event"
|
||||
|
||||
@ -4914,9 +4905,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1702902297"] = "Introduction"
|
||||
-- Vision
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2183503084"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
||||
|
||||
-- Let's get started
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Let's get started"
|
||||
|
||||
@ -4941,6 +4929,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective"
|
||||
-- Flexibility
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility"
|
||||
|
||||
-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3892227145"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities."
|
||||
|
||||
-- Privacy
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy"
|
||||
|
||||
@ -5898,6 +5889,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- Executable Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
|
||||
|
||||
-- All Audio Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
|
||||
|
||||
-- All Video Files
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files"
|
||||
|
||||
@ -6027,6 +6021,24 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T878007824"]
|
||||
-- Please enter the secret necessary for authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::DATASOURCEVALIDATION::T968385876"] = "Please enter the secret necessary for authentication."
|
||||
|
||||
-- Unsupported image format
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1398282880"] = "Unsupported image format"
|
||||
|
||||
-- File has no extension
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T1555980031"] = "File has no extension"
|
||||
|
||||
-- Audio files are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2919730669"] = "Audio files are not supported yet"
|
||||
|
||||
-- Videos are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T2928927510"] = "Videos are not supported yet"
|
||||
|
||||
-- Images are not supported yet
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T298062956"] = "Images are not supported yet"
|
||||
|
||||
-- Executables are not allowed
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::FILEEXTENSIONVALIDATION::T4167762413"] = "Executables are not allowed"
|
||||
|
||||
-- The hostname is not a valid HTTP(S) URL.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T1013354736"] = "The hostname is not a valid HTTP(S) URL."
|
||||
|
||||
@ -6064,4 +6076,4 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::VALIDATION::PROVIDERVALIDATION::T818893091"] =
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T1307384014"] = "Unnamed workspace"
|
||||
|
||||
-- Delete Chat
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::WORKSPACEBEHAVIOUR::T2244038752"] = "Delete Chat"
|
||||
|
||||
@ -15,7 +15,8 @@ public enum LLMProviders
|
||||
DEEP_SEEK = 11,
|
||||
ALIBABA_CLOUD = 12,
|
||||
PERPLEXITY = 14,
|
||||
|
||||
OPEN_ROUTER = 15,
|
||||
|
||||
FIREWORKS = 5,
|
||||
GROQ = 6,
|
||||
HUGGINGFACE = 13,
|
||||
|
||||
@ -9,6 +9,7 @@ using AIStudio.Provider.Helmholtz;
|
||||
using AIStudio.Provider.HuggingFace;
|
||||
using AIStudio.Provider.Mistral;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.OpenRouter;
|
||||
using AIStudio.Provider.Perplexity;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
using AIStudio.Provider.X;
|
||||
@ -42,7 +43,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => "DeepSeek",
|
||||
LLMProviders.ALIBABA_CLOUD => "Alibaba Cloud",
|
||||
LLMProviders.PERPLEXITY => "Perplexity",
|
||||
|
||||
LLMProviders.OPEN_ROUTER => "OpenRouter",
|
||||
|
||||
LLMProviders.GROQ => "Groq",
|
||||
LLMProviders.FIREWORKS => "Fireworks.ai",
|
||||
LLMProviders.HUGGINGFACE => "Hugging Face",
|
||||
@ -92,7 +94,9 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.ALIBABA_CLOUD => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://www.alibabacloud.com/help/en/model-studio/support/faq-about-alibaba-cloud-model-studio").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.PERPLEXITY => Confidence.USA_NO_TRAINING.WithRegion("America, U.S.").WithSources("https://www.perplexity.ai/hub/legal/perplexity-api-terms-of-service").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
|
||||
LLMProviders.OPEN_ROUTER => Confidence.USA_HUB.WithRegion("America, U.S.").WithSources("https://openrouter.ai/privacy", "https://openrouter.ai/terms").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.SELF_HOSTED => Confidence.SELF_HOSTED.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
|
||||
LLMProviders.HELMHOLTZ => Confidence.GDPR_NO_TRAINING.WithRegion("Europe, Germany").WithSources("https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)),
|
||||
@ -128,7 +132,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => false,
|
||||
LLMProviders.HUGGINGFACE => false,
|
||||
LLMProviders.PERPLEXITY => false,
|
||||
|
||||
LLMProviders.OPEN_ROUTER => true,
|
||||
|
||||
//
|
||||
// Self-hosted providers are treated as a special case anyway.
|
||||
//
|
||||
@ -171,7 +176,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => new ProviderDeepSeek { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.PERPLEXITY => new ProviderPerplexity { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
|
||||
LLMProviders.OPEN_ROUTER => new ProviderOpenRouter { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
|
||||
LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
LLMProviders.HUGGINGFACE => new ProviderHuggingFace(inferenceProvider, model) { InstanceName = instanceName, AdditionalJsonApiParameters = expertProviderApiParameter },
|
||||
@ -201,7 +207,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/sign_up",
|
||||
LLMProviders.ALIBABA_CLOUD => "https://account.alibabacloud.com/register/intl_register.htm",
|
||||
LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api",
|
||||
|
||||
LLMProviders.OPEN_ROUTER => "https://openrouter.ai/keys",
|
||||
|
||||
LLMProviders.GROQ => "https://console.groq.com/",
|
||||
LLMProviders.FIREWORKS => "https://fireworks.ai/login",
|
||||
LLMProviders.HUGGINGFACE => "https://huggingface.co/login",
|
||||
@ -224,8 +231,9 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/usage",
|
||||
LLMProviders.ALIBABA_CLOUD => "https://usercenter2-intl.aliyun.com/billing",
|
||||
LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api/",
|
||||
LLMProviders.OPEN_ROUTER => "https://openrouter.ai/activity",
|
||||
LLMProviders.HUGGINGFACE => "https://huggingface.co/settings/billing",
|
||||
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
@ -241,8 +249,9 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
LLMProviders.OPEN_ROUTER => true,
|
||||
LLMProviders.HUGGINGFACE => true,
|
||||
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@ -288,7 +297,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
|
||||
LLMProviders.OPEN_ROUTER => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
LLMProviders.HELMHOLTZ => true,
|
||||
@ -310,7 +320,8 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.DEEP_SEEK => true,
|
||||
LLMProviders.ALIBABA_CLOUD => true,
|
||||
LLMProviders.PERPLEXITY => true,
|
||||
|
||||
LLMProviders.OPEN_ROUTER => true,
|
||||
|
||||
LLMProviders.GROQ => true,
|
||||
LLMProviders.FIREWORKS => true,
|
||||
LLMProviders.HELMHOLTZ => true,
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Provider.OpenRouter;
|
||||
|
||||
/// <summary>
|
||||
/// A data model for an OpenRouter model from the API.
|
||||
/// </summary>
|
||||
/// <param name="Id">The model's ID.</param>
|
||||
/// <param name="Name">The model's human-readable display name.</param>
|
||||
public readonly record struct OpenRouterModel(string Id, string? Name);
|
||||
@ -0,0 +1,7 @@
|
||||
namespace AIStudio.Provider.OpenRouter;
|
||||
|
||||
/// <summary>
|
||||
/// A data model for the response from the OpenRouter models endpoint.
|
||||
/// </summary>
|
||||
/// <param name="Data">The list of models.</param>
|
||||
public readonly record struct OpenRouterModelsResponse(IList<OpenRouterModel> Data);
|
||||
202
app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs
Normal file
202
app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs
Normal file
@ -0,0 +1,202 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Provider.OpenRouter;
|
||||
|
||||
public sealed class ProviderOpenRouter() : BaseProvider("https://openrouter.ai/api/v1/", LOGGER)
|
||||
{
|
||||
private const string PROJECT_WEBSITE = "https://github.com/MindWorkAI/AI-Studio";
|
||||
private const string PROJECT_NAME = "MindWork AI Studio";
|
||||
|
||||
private static readonly ILogger<ProviderOpenRouter> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderOpenRouter>();
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id => LLMProviders.OPEN_ROUTER.ToName();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string InstanceName { get; set; } = "OpenRouter";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
// Prepare the system prompt:
|
||||
var systemPrompt = new Message
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
var apiParameters = this.ParseAdditionalApiParameters();
|
||||
|
||||
// Build the list of messages:
|
||||
var messages = await chatThread.Blocks.BuildMessages(async n => new Message
|
||||
{
|
||||
Role = n.Role switch
|
||||
{
|
||||
ChatRole.USER => "user",
|
||||
ChatRole.AI => "assistant",
|
||||
ChatRole.AGENT => "assistant",
|
||||
ChatRole.SYSTEM => "system",
|
||||
|
||||
_ => "user",
|
||||
},
|
||||
|
||||
Content = n.Content switch
|
||||
{
|
||||
ContentText text => await text.PrepareContentForAI(),
|
||||
_ => string.Empty,
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare the OpenRouter HTTP chat request:
|
||||
var openRouterChatRequest = JsonSerializer.Serialize(new ChatCompletionAPIRequest
|
||||
{
|
||||
Model = chatModel.Id,
|
||||
|
||||
// Build the messages:
|
||||
// - First of all the system prompt
|
||||
// - Then none-empty user and AI messages
|
||||
Messages = [systemPrompt, ..messages],
|
||||
|
||||
// Right now, we only support streaming completions:
|
||||
Stream = true,
|
||||
AdditionalApiParameters = apiParameters
|
||||
}, JSON_SERIALIZER_OPTIONS);
|
||||
|
||||
async Task<HttpRequestMessage> RequestBuilder()
|
||||
{
|
||||
// Build the HTTP post request:
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
|
||||
|
||||
// Set the authorization header:
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
|
||||
// Set custom headers for project identification:
|
||||
request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE);
|
||||
request.Headers.Add("X-Title", PROJECT_NAME);
|
||||
|
||||
// Set the content:
|
||||
request.Content = new StringContent(openRouterChatRequest, Encoding.UTF8, "application/json");
|
||||
return request;
|
||||
}
|
||||
|
||||
await foreach (var content in this.StreamChatCompletionInternal<ChatCompletionDeltaStreamLine, NoChatCompletionAnnotationStreamLine>("OpenRouter", RequestBuilder, token))
|
||||
yield return content;
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadModels(token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return this.LoadEmbeddingModels(token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
if (secretKey is null)
|
||||
return [];
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "models");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
|
||||
|
||||
// Set custom headers for project identification:
|
||||
request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE);
|
||||
request.Headers.Add("X-Title", PROJECT_NAME);
|
||||
|
||||
using var response = await this.httpClient.SendAsync(request, token);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var modelResponse = await response.Content.ReadFromJsonAsync<OpenRouterModelsResponse>(token);
|
||||
|
||||
// Filter out non-text models (image, audio, embedding models) and convert to Model
|
||||
return modelResponse.Data
|
||||
.Where(n =>
|
||||
!n.Id.Contains("whisper", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("dall-e", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("tts", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("embedding", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("moderation", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("stable-diffusion", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("flux", StringComparison.OrdinalIgnoreCase) &&
|
||||
!n.Id.Contains("midjourney", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(n => new Model(n.Id, n.Name));
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<Model>> LoadEmbeddingModels(CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
}
|
||||
};
|
||||
|
||||
if (secretKey is null)
|
||||
return [];
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "embeddings/models");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
|
||||
|
||||
// Set custom headers for project identification:
|
||||
request.Headers.Add("HTTP-Referer", PROJECT_WEBSITE);
|
||||
request.Headers.Add("X-Title", PROJECT_NAME);
|
||||
|
||||
using var response = await this.httpClient.SendAsync(request, token);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
return [];
|
||||
|
||||
var modelResponse = await response.Content.ReadFromJsonAsync<OpenRouterModelsResponse>(token);
|
||||
|
||||
// Convert all embedding models to Model
|
||||
return modelResponse.Data.Select(n => new Model(n.Id, n.Name));
|
||||
}
|
||||
}
|
||||
@ -143,6 +143,17 @@ public static partial class ProviderExtensions
|
||||
Capability.RESPONSES_API, Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
if(modelName is "gpt-5.2" || modelName.StartsWith("gpt-5.2-"))
|
||||
return
|
||||
[
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT, Capability.IMAGE_OUTPUT,
|
||||
|
||||
Capability.FUNCTION_CALLING, Capability.OPTIONAL_REASONING,
|
||||
Capability.WEB_SEARCH,
|
||||
Capability.RESPONSES_API, Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
return
|
||||
[
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
|
||||
249
app/MindWork AI Studio/Settings/ProviderExtensions.OpenRouter.cs
Normal file
249
app/MindWork AI Studio/Settings/ProviderExtensions.OpenRouter.cs
Normal file
@ -0,0 +1,249 @@
|
||||
using AIStudio.Provider;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
public static partial class ProviderExtensions
|
||||
{
|
||||
public static List<Capability> GetModelCapabilitiesOpenRouter(Model model)
|
||||
{
|
||||
var modelName = model.Id.ToLowerInvariant().AsSpan();
|
||||
|
||||
//
|
||||
// OpenRouter model IDs follow the pattern: "provider/model-name"
|
||||
// Examples:
|
||||
// - openai/gpt-4o
|
||||
// - anthropic/claude-3-5-sonnet
|
||||
// - google/gemini-pro-1.5
|
||||
// - meta-llama/llama-3.1-405b-instruct
|
||||
//
|
||||
// We need to detect capabilities based on both provider and model name.
|
||||
//
|
||||
|
||||
//
|
||||
// OpenAI models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("openai/") is not -1)
|
||||
{
|
||||
// Reasoning models (o1, o3, o4 series)
|
||||
if (modelName.IndexOf("/o1") is not -1 ||
|
||||
modelName.IndexOf("/o3") is not -1 ||
|
||||
modelName.IndexOf("/o4") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.ALWAYS_REASONING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// GPT-4o and GPT-5 series with multimodal
|
||||
if (modelName.IndexOf("/gpt-4o") is not -1 ||
|
||||
modelName.IndexOf("/gpt-5") is not -1 ||
|
||||
modelName.IndexOf("/chatgpt-4o") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Standard GPT-4
|
||||
if (modelName.IndexOf("/gpt-4") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// GPT-3.5
|
||||
if (modelName.IndexOf("/gpt-3.5") is not -1 ||
|
||||
modelName.IndexOf("/gpt-3") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Anthropic models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("anthropic/") is not -1)
|
||||
{
|
||||
// Claude 3.5 and newer with vision
|
||||
if (modelName.IndexOf("/claude-3.5") is not -1 ||
|
||||
modelName.IndexOf("/claude-3-5") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Claude 3 Opus/Sonnet with vision
|
||||
if (modelName.IndexOf("/claude-3-opus") is not -1 ||
|
||||
modelName.IndexOf("/claude-3-sonnet") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Other Claude 3 models
|
||||
if (modelName.IndexOf("/claude-3") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Google models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("google/") is not -1)
|
||||
{
|
||||
// Gemini models with multimodal
|
||||
if (modelName.IndexOf("/gemini") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// xAI Grok models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("x-ai/") is not -1 || modelName.IndexOf("/grok") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("-vision") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// DeepSeek models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("/deepseek") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("-r1") is not -1 || modelName.IndexOf(" r1") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.ALWAYS_REASONING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Mistral models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("/mistral") is not -1 || modelName.IndexOf("/pixtral") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("/pixtral") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Meta Llama models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("/llama") is not -1)
|
||||
{
|
||||
// Llama 4 with vision
|
||||
if (modelName.IndexOf("/llama-4") is not -1 ||
|
||||
modelName.IndexOf("/llama4") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Vision models
|
||||
if (modelName.IndexOf("-vision") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Llama 3.1+ with function calling
|
||||
if (modelName.IndexOf("/llama-3.") is not -1 ||
|
||||
modelName.IndexOf("/llama3.") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.FUNCTION_CALLING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
// Default Llama
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Qwen models via OpenRouter:
|
||||
//
|
||||
if (modelName.IndexOf("/qwen") is not -1 || modelName.IndexOf("/qwq") is not -1)
|
||||
{
|
||||
if (modelName.IndexOf("/qwq") is not -1)
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.ALWAYS_REASONING,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
|
||||
//
|
||||
// Default for unknown models:
|
||||
// Assume basic text input/output with chat completion
|
||||
//
|
||||
return [
|
||||
Capability.TEXT_INPUT,
|
||||
Capability.TEXT_OUTPUT,
|
||||
Capability.CHAT_COMPLETION_API,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,8 @@ public static partial class ProviderExtensions
|
||||
LLMProviders.DEEP_SEEK => GetModelCapabilitiesDeepSeek(provider.Model),
|
||||
LLMProviders.ALIBABA_CLOUD => GetModelCapabilitiesAlibaba(provider.Model),
|
||||
LLMProviders.PERPLEXITY => GetModelCapabilitiesPerplexity(provider.Model),
|
||||
|
||||
LLMProviders.OPEN_ROUTER => GetModelCapabilitiesOpenRouter(provider.Model),
|
||||
|
||||
LLMProviders.GROQ => GetModelCapabilitiesOpenSource(provider.Model),
|
||||
LLMProviders.FIREWORKS => GetModelCapabilitiesOpenSource(provider.Model),
|
||||
LLMProviders.HUGGINGFACE => GetModelCapabilitiesOpenSource(provider.Model),
|
||||
|
||||
62
app/MindWork AI Studio/Tools/ImageHelpers.cs
Normal file
62
app/MindWork AI Studio/Tools/ImageHelpers.cs
Normal file
@ -0,0 +1,62 @@
|
||||
namespace AIStudio.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for image handling, particularly for Base64 images.
|
||||
/// </summary>
|
||||
public static class ImageHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects the MIME type of an image from its Base64-encoded header.
|
||||
/// </summary>
|
||||
/// <param name="base64ImageString">The Base64-encoded image string.</param>
|
||||
/// <returns>The detected MIME type (e.g., "image/png", "image/jpeg").</returns>
|
||||
public static string DetectMimeType(ReadOnlySpan<char> base64ImageString)
|
||||
{
|
||||
if (base64ImageString.IsWhiteSpace() || base64ImageString.Length < 10)
|
||||
return "image"; // Fallback
|
||||
|
||||
var header = base64ImageString[..Math.Min(20, base64ImageString.Length)];
|
||||
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/List_of_file_signatures
|
||||
//
|
||||
|
||||
// PNG: iVBORw0KGgo
|
||||
if (header.StartsWith("iVBORw0KGgo", StringComparison.Ordinal))
|
||||
return "image/png";
|
||||
|
||||
// JPEG: /9j/
|
||||
if (header.StartsWith("/9j/", StringComparison.Ordinal))
|
||||
return "image/jpeg";
|
||||
|
||||
// GIF: R0lGOD
|
||||
if (header.StartsWith("R0lGOD", StringComparison.Ordinal))
|
||||
return "image/gif";
|
||||
|
||||
// WebP: UklGR
|
||||
if (header.StartsWith("UklGR", StringComparison.Ordinal))
|
||||
return "image/webp";
|
||||
|
||||
// BMP: Qk
|
||||
if (header.StartsWith("Qk", StringComparison.Ordinal))
|
||||
return "image/bmp";
|
||||
|
||||
// Default fallback:
|
||||
return "image";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Base64 string to a data URL suitable for use in HTML img src attributes.
|
||||
/// </summary>
|
||||
/// <param name="base64String">The Base64-encoded image string.</param>
|
||||
/// <param name="mimeType">Optional MIME type. If not provided, it will be auto-detected.</param>
|
||||
/// <returns>A data URL in the format "data:image/type;base64,..."</returns>
|
||||
public static string ToDataUrl(string base64String, string? mimeType = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(base64String))
|
||||
return string.Empty;
|
||||
|
||||
var detectedMimeType = mimeType ?? DetectMimeType(base64String);
|
||||
return $"data:{detectedMimeType};base64,{base64String}";
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,9 @@ public readonly record struct FileTypeFilter(string FilterName, string[] FilterE
|
||||
|
||||
public static FileTypeFilter AllImages => new(TB("All Image Files"), ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "heic"]);
|
||||
|
||||
public static FileTypeFilter AllVideos => new(TB("All Video Files"), ["mp4", "avi", "mkv", "mov", "wmv", "flv", "webm"]);
|
||||
public static FileTypeFilter AllVideos => new(TB("All Video Files"), ["mp4", "m4v", "avi", "mkv", "mov", "wmv", "flv", "webm"]);
|
||||
|
||||
public static FileTypeFilter AllAudio => new(TB("All Audio Files"), ["mp3", "wav", "wave", "aac", "flac", "ogg", "m4a", "wma", "alac", "aiff", "m4b"]);
|
||||
|
||||
public static FileTypeFilter Executables => new(TB("Executable Files"), ["exe", "app", "bin", "appimage"]);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for selecting multiple files.
|
||||
/// </summary>
|
||||
/// <param name="UserCancelled">Was the file selection canceled?</param>
|
||||
/// <param name="SelectedFilePaths">The selected files, if any.</param>
|
||||
public readonly record struct FilesSelectionResponse(bool UserCancelled, IReadOnlyList<string> SelectedFilePaths);
|
||||
@ -1,12 +1,9 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
public sealed partial class RustService
|
||||
{
|
||||
private static string TB_APIKeys(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.APIKeys");
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the API key for the given secret ID.
|
||||
/// </summary>
|
||||
@ -15,8 +12,6 @@ public sealed partial class RustService
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetAPIKey(ISecretId secretId, bool isTrying = false)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
|
||||
|
||||
var secretRequest = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, isTrying);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@ -41,8 +36,6 @@ public sealed partial class RustService
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> SetAPIKey(ISecretId secretId, string key)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
|
||||
|
||||
var encryptedKey = await this.encryptor!.Encrypt(key);
|
||||
var request = new StoreSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, encryptedKey);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||
@ -66,8 +59,6 @@ public sealed partial class RustService
|
||||
/// <returns>The delete secret response.</returns>
|
||||
public async Task<DeleteSecretResponse> DeleteAPIKey(ISecretId secretId)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_APIKeys(fallbackEN);
|
||||
|
||||
var request = new SelectSecretRequest($"provider::{secretId.SecretId}::{secretId.SecretName}::api_key", Environment.UserName, false);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
public sealed partial class RustService
|
||||
{
|
||||
private static string TB_Clipboard(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.Clipboard");
|
||||
|
||||
/// <summary>
|
||||
/// Tries to copy the given text to the clipboard.
|
||||
/// </summary>
|
||||
@ -14,8 +11,6 @@ public sealed partial class RustService
|
||||
/// <param name="text">The text to copy to the clipboard.</param>
|
||||
public async Task CopyText2Clipboard(ISnackbar snackbar, string text)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_Clipboard(fallbackEN);
|
||||
|
||||
var message = TB("Successfully copied the text to your clipboard");
|
||||
var iconColor = Color.Error;
|
||||
var severity = Severity.Error;
|
||||
|
||||
@ -25,17 +25,36 @@ public sealed partial class RustService
|
||||
PreviousFile = initialFile is null ? null : new (initialFile),
|
||||
Filter = filter
|
||||
};
|
||||
|
||||
|
||||
var result = await this.http.PostAsJsonAsync("/select/file", payload, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to select a file: '{result.StatusCode}'");
|
||||
return new FileSelectionResponse(true, string.Empty);
|
||||
}
|
||||
|
||||
|
||||
return await result.Content.ReadFromJsonAsync<FileSelectionResponse>(this.jsonRustSerializerOptions);
|
||||
}
|
||||
|
||||
public async Task<FilesSelectionResponse> SelectFiles(string title, FileTypeFilter? filter = null, string? initialFile = null)
|
||||
{
|
||||
var payload = new SelectFileOptions
|
||||
{
|
||||
Title = title,
|
||||
PreviousFile = initialFile is null ? null : new (initialFile),
|
||||
Filter = filter
|
||||
};
|
||||
|
||||
var result = await this.http.PostAsJsonAsync("/select/files", payload, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to select files: '{result.StatusCode}'");
|
||||
return new FilesSelectionResponse(true, Array.Empty<string>());
|
||||
}
|
||||
|
||||
return await result.Content.ReadFromJsonAsync<FilesSelectionResponse>(this.jsonRustSerializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a dialog to let the user select a file for a writing operation.
|
||||
/// </summary>
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
public sealed partial class RustService
|
||||
{
|
||||
private static string TB_Secrets(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, $"{nameof(RustService)}.Secrets");
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the secret data for the given secret ID.
|
||||
/// </summary>
|
||||
@ -15,8 +12,6 @@ public sealed partial class RustService
|
||||
/// <returns>The requested secret.</returns>
|
||||
public async Task<RequestedSecret> GetSecret(ISecretId secretId, bool isTrying = false)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
|
||||
|
||||
var secretRequest = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, isTrying);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@ -41,8 +36,6 @@ public sealed partial class RustService
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> SetSecret(ISecretId secretId, string secretData)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
|
||||
|
||||
var encryptedSecret = await this.encryptor!.Encrypt(secretData);
|
||||
var request = new StoreSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, encryptedSecret);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||
@ -66,8 +59,6 @@ public sealed partial class RustService
|
||||
/// <returns>The delete secret response.</returns>
|
||||
public async Task<DeleteSecretResponse> DeleteSecret(ISecretId secretId)
|
||||
{
|
||||
static string TB(string fallbackEN) => TB_Secrets(fallbackEN);
|
||||
|
||||
var request = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, false);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using Version = System.Version;
|
||||
|
||||
@ -14,6 +15,8 @@ namespace AIStudio.Tools.Services;
|
||||
/// </summary>
|
||||
public sealed partial class RustService : BackgroundService
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(RustService).Namespace, nameof(RustService));
|
||||
|
||||
private readonly HttpClient http;
|
||||
|
||||
private readonly JsonSerializerOptions jsonRustSerializerOptions = new()
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Provides centralized validation for file extensions.
|
||||
/// </summary>
|
||||
public static class FileExtensionValidation
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExtensionValidation).Namespace, nameof(FileExtensionValidation));
|
||||
|
||||
/// <summary>
|
||||
/// Validates the file extension and sends appropriate MessageBus notifications when invalid.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to validate.</param>
|
||||
/// <returns>True if valid, false if invalid (error/warning already sent via MessageBus).</returns>
|
||||
public static async Task<bool> IsExtensionValidWithNotifyAsync(string filePath)
|
||||
{
|
||||
var ext = Path.GetExtension(filePath).TrimStart('.');
|
||||
if (Array.Exists(FileTypeFilter.Executables.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(
|
||||
Icons.Material.Filled.AppBlocking,
|
||||
TB("Executables are not allowed")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllImages.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(
|
||||
Icons.Material.Filled.ImageNotSupported,
|
||||
TB("Images are not supported yet")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllVideos.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(
|
||||
Icons.Material.Filled.FeaturedVideo,
|
||||
TB("Videos are not supported yet")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.Exists(FileTypeFilter.AllAudio.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendWarning(new(
|
||||
Icons.Material.Filled.AudioFile,
|
||||
TB("Audio files are not supported yet")));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the file is a supported image format and sends appropriate MessageBus notifications when invalid.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to validate.</param>
|
||||
/// <returns>True if valid image, false if invalid (error already sent via MessageBus).</returns>
|
||||
public static async Task<bool> IsImageExtensionValidWithNotifyAsync(string filePath)
|
||||
{
|
||||
var ext = Path.GetExtension(filePath).TrimStart('.');
|
||||
if (string.IsNullOrWhiteSpace(ext))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(
|
||||
Icons.Material.Filled.ImageNotSupported,
|
||||
TB("File has no extension")));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Array.Exists(FileTypeFilter.AllImages.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(
|
||||
Icons.Material.Filled.ImageNotSupported,
|
||||
TB("Unsupported image format")));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -4,16 +4,14 @@
|
||||
"net9.0": {
|
||||
"CodeBeam.MudBlazor.Extensions": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.2.5, )",
|
||||
"resolved": "8.2.5",
|
||||
"contentHash": "zZ2zFQeGAqrT0rCE8ZlfnchBUk8IEwFVgZ2mWVHy8EfAQHvgUXHvc6l/t51n1Wx9DMP8beWRDTM6nO1kfYAXZg==",
|
||||
"requested": "[8.3.0, )",
|
||||
"resolved": "8.3.0",
|
||||
"contentHash": "kp42Wmz4UroTbrpb5Ak8U/VYbdxg+35W8gg4Q4SOQMV1GgSWRHfoDf0gXVEEW9Lsx3/wZgzxvGwcs3brhQbZQg==",
|
||||
"dependencies": {
|
||||
"BuildBundlerMinifier": "3.2.449",
|
||||
"CsvHelper": "33.0.1",
|
||||
"Microsoft.AspNetCore.Components": "9.0.10",
|
||||
"Microsoft.AspNetCore.Components.Web": "9.0.10",
|
||||
"MudBlazor": "8.0.0",
|
||||
"ZXing.Net": "0.16.9"
|
||||
"Microsoft.AspNetCore.Components": "9.0.11",
|
||||
"Microsoft.AspNetCore.Components.Web": "9.0.11",
|
||||
"MudBlazor": "8.0.0"
|
||||
}
|
||||
},
|
||||
"HtmlAgilityPack": {
|
||||
@ -24,9 +22,9 @@
|
||||
},
|
||||
"LuaCSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.4.2, )",
|
||||
"resolved": "0.4.2",
|
||||
"contentHash": "wS0hp7EFx+llJ/U/7Ykz4FSmQf8DH4mNejwo5/h1KuFyguzGZbKhTO22X54pXnuqa5cIKfEfQ29dluHHnCX05Q=="
|
||||
"requested": "[0.5.0, )",
|
||||
"resolved": "0.5.0",
|
||||
"contentHash": "F2dco41jmVwntVJeF4LOknomgNjjVMAZOPgIOJQdObxUQrbNiKJulH4ZInWiZXhHSHpwhocVVjepwzfa8BpfMA=="
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Embedded": {
|
||||
"type": "Direct",
|
||||
@ -45,9 +43,9 @@
|
||||
},
|
||||
"MudBlazor": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.12.0, )",
|
||||
"resolved": "8.12.0",
|
||||
"contentHash": "ZwgHPt2DwiQoFeP8jxPzNEsUmJF17ljtospVH+uMUKUKpklz6jEkdE5vNs7PnHaPH9HEbpFEQgJw8QPlnFZjsQ==",
|
||||
"requested": "[8.15.0, )",
|
||||
"resolved": "8.15.0",
|
||||
"contentHash": "iOJEnQ6tYGQPfPJaUazyC8H6pcczgaMX7vhUzrJPpB0WqEXNozwMfSzoOe2/JZmVWJcUfYZgKBeBU2Z27XY7Sw==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "9.0.1",
|
||||
"Microsoft.AspNetCore.Components.Web": "9.0.1",
|
||||
@ -78,11 +76,6 @@
|
||||
"resolved": "3.2.449",
|
||||
"contentHash": "uA9sYDy4VepL3xwzBTLcP2LyuVYMt0ZIT3gaSiXvGoX15Ob+rOP+hGydhevlSVd+rFo+Y+VQFEHDuWU8HBW+XA=="
|
||||
},
|
||||
"CsvHelper": {
|
||||
"type": "Transitive",
|
||||
"resolved": "33.0.1",
|
||||
"contentHash": "fev4lynklAU2A9GVMLtwarkwaanjSYB4wUqO2nOJX5hnzObORzUqVLe+bDYCUyIIRQM4o5Bsq3CcyJR89iMmEQ=="
|
||||
},
|
||||
"Markdig": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.41.3",
|
||||
@ -90,65 +83,65 @@
|
||||
},
|
||||
"Microsoft.AspNetCore.Authorization": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "odY40/4vXt1tHeuc89zjEPfx0i0c2jurKW9r884v92i6BGasJkCKTtnIGIREBqnTn+HB4uZLipOdWG/GczQwnQ==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "VtzQJnpvemh+n4jjMJR+XWiupCWu+Em822orJIFF9jXRfrJET1fBTo6yWqNFnQQKqtvQ3E4Vrjq0N/bHdZR25w==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Metadata": "9.0.10",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.10",
|
||||
"Microsoft.Extensions.Options": "9.0.10"
|
||||
"Microsoft.AspNetCore.Metadata": "9.0.11",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.11",
|
||||
"Microsoft.Extensions.Options": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "yodHFmpceXlUrWJ53OgzWyoZWvxNFtz8pGAeDXYenZau1UD5nR2uNGMt1QeeA/3LwysnR1JehndthS587P5GrQ==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "HIMOsBnuhcTNWBnCyugRHVgrh4iYfIM2Hl+8+8SQ6wrEIk+I2fUKa8USwV8XaMvZsjyJD7fPHaldAPoR9BNAVA==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Authorization": "9.0.10",
|
||||
"Microsoft.AspNetCore.Components.Analyzers": "9.0.10"
|
||||
"Microsoft.AspNetCore.Authorization": "9.0.11",
|
||||
"Microsoft.AspNetCore.Components.Analyzers": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "VWSbgP3XaUYdt8JwUOOyx64JI6dLgClYRyiJ6+XcuC/2OW0eKSee3VJwq/1jutZqkAzyBjQ/gpDw7BXmbhrlVA=="
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "7345cqEUev15rfdBeoBmYtUFrdHyYP/FVUbva3Q66eFTT0UuaykqWPtHmnv8R8GJZ9xntMxKhmqhySZTnth7sQ=="
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Forms": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "wVyZxxu8C/P0h4QifYEsVJ4AGWOd9oPtmHa0cUbG43JJ8p1oDu9pvZucJc0MjE7GlX/vmr/HntyvkGN9geL6cg==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "emoJb7TUrOy27stNXcXv+WsyItn0Qhe4gMcZHtfIfcnErRjN2YOotCAKWezBeKvYWqaV28qsDpjxvjjcANxZGw==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "9.0.10"
|
||||
"Microsoft.AspNetCore.Components": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Components.Web": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "1yay2fD17JGdSx/U1eeke8ONd0xuJJgpYVk0OKpOaomULRPAP/XTk4IUb4JNpoVhKEoM25y7R/RSXO2So7YTBA==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "wU9oVKqHfP4euIpRiRHLVkmhjFXctOvRDHOMUdBRnUErUua+fE6KsIRuBvUKla6tIql9sS58wbETZnjZpnX0lw==",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "9.0.10",
|
||||
"Microsoft.AspNetCore.Components.Forms": "9.0.10",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.10",
|
||||
"Microsoft.Extensions.Primitives": "9.0.10",
|
||||
"Microsoft.JSInterop": "9.0.10"
|
||||
"Microsoft.AspNetCore.Components": "9.0.11",
|
||||
"Microsoft.AspNetCore.Components.Forms": "9.0.11",
|
||||
"Microsoft.Extensions.DependencyInjection": "9.0.11",
|
||||
"Microsoft.Extensions.Primitives": "9.0.11",
|
||||
"Microsoft.JSInterop": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "JY5XyecFnIvCMZrtUaI2IrZY/SYidTqTN7H+tXmXxdGlvRGGnf2uUKH47MJu9poJ/raK4SWK5uZQwhd21T2WFw=="
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "O0HzG5utNH6ihO632k0nHFZa8iNDmGphdgWWqeDSdN/T9n0ZOXlA5+q77DxY3nHTjNfA0KMfpykIhEI+Wmzosg=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "iEtXCkNd5XhjNJAOb/wO4IhDRdLIE2CsPxZggZQWJ/q2+sa8dmEPC393nnsiqdH8/4KV8Xn25IzgKPR1UEQ0og==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "UquyDzvz0EneIQrrU67GJkIgynS+VD7t+RDtNv6VgKMOFrLBjldn6hzlXppGGecFMvAkMTqn4T8RYvzw7j7fQA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "r9waLiOPe9ZF1PvzUT+RDoHvpMmY8MW+lb4lqjYGObwKpnyPMLI3odVvlmshwuZcdoHynsGWOrCPA0hxZ63lIA=="
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "+ZxxZzcVU+IEzq12GItUzf/V3mEc5nSLiXijwvDc4zyhbjvSZZ043giSZqGnhakrjwRWjkerIHPrRwm9okEIpw=="
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
@ -176,19 +169,19 @@
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "MFUPv/nN1rAQ19w43smm6bbf0JDYN/1HEPHoiMYY50pvDMFpglzWAuoTavByDmZq7UuhjaxwrET3joU69ZHoHQ==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "UKWFTDwtZQIoypyt1YPVsxTnDK+0sKn26+UeSGeNlkRQddrkt9EC6kP4g94rgO/WOZkz94bKNlF1dVZN3QfPFQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "zMNABt8eBv0B0XrWjFy9nZNgddavaOeq3ZdaD5IlHhRH65MrU7HM+Hd8GjWE3e2VDGFPZFfSAc6XVXC17f9fOA==",
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "HX4M3BLkW1dtByMKHDVq6r7Jy6e4hf8NDzHpIgz7C8BtYk9JQHhfYX5c1UheQTD5Veg1yBhz/cD9C8vtrGrk9w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.10",
|
||||
"Microsoft.Extensions.Primitives": "9.0.10"
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.11",
|
||||
"Microsoft.Extensions.Primitives": "9.0.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
@ -198,13 +191,8 @@
|
||||
},
|
||||
"Microsoft.JSInterop": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.10",
|
||||
"contentHash": "+Zxxwp8rspdxq6uIkaEtqWW/vljDr2tLiLuhPUYV0+CzeuFpuwcKJ95iz6L9xbakxqjZN3WjmJBtqWZfB+zC5A=="
|
||||
},
|
||||
"ZXing.Net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.16.9",
|
||||
"contentHash": "7WaVMHklpT3Ye2ragqRIwlFRsb6kOk63BOGADV0fan3ulVfGLUYkDi5yNUsZS/7FVNkWbtHAlDLmu4WnHGfqvQ=="
|
||||
"resolved": "9.0.11",
|
||||
"contentHash": "5w/W57cXjt8Ugp5COQCsv1R/wt7KzZXjbTqK4AFvgsxqmv1DFJ6OzagzJmwgp6unczFuff6t8wNi+URePV6PYQ=="
|
||||
},
|
||||
"sharedtools": {
|
||||
"type": "Project"
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
# v0.9.55, build 230 (2025-12-xx xx:xx UTC)
|
||||
- Added support for newer Mistral models (Mistral 3, Voxtral, and Magistral).
|
||||
- Added support for the new OpenAI model GPT 5.2.
|
||||
- Added support for OpenRouter as LLM and embedding provider.
|
||||
- Added a description field to local data sources (preview feature) so that the data selection agent has more information about which data each local source contains when selecting data sources.
|
||||
- Added the ability to use file attachments in chat. This is the initial implementation of this feature. We will continue to develop this feature and refine it further based on user feedback. Many thanks to Sabrina `Sabrina-devops` for this wonderful contribution.
|
||||
- Improved the document analysis assistant (in preview) by adding descriptions to the different sections.
|
||||
- Improved the document preview dialog for the document analysis assistant (in preview), providing Markdown and plain text views for attached files.
|
||||
- Improved the Pandoc handling for the document analysis assistant (in preview) and file attachments in chat. When Pandoc is not installed and users attempt to attach files, users are now prompted to install Pandoc first.
|
||||
- Improved the ID handling for configuration plugins.
|
||||
- Improved error handling, logging, and code quality.
|
||||
- Improved error handling for Microsoft Word export.
|
||||
- Improved file reading, e.g. for the translation, summarization, and legal assistants, by performing the Pandoc validation in the first step. This prevents unnecessary selection of files that cannot be processed.
|
||||
- Improved the file selection for file attachments in chat and assistant file loading by filtering out audio files. Audio attachments are not yet supported.
|
||||
- Improved the developer experience by automating localization updates in the filesystem for the selected language in the localization assistant.
|
||||
- Improved the file selection so that users can now select multiple files at the same time. This is useful, for example, for document analysis (in preview) or adding file attachments to the chat.
|
||||
- Fixed a bug in the local data sources info dialog (preview feature) for data directories that could cause the app to crash. The error was caused by a background thread producing data while the frontend attempted to display it.
|
||||
- Fixed a visual bug where a function's preview status was misaligned. You might have seen it in document analysis or the ERI server assistant.
|
||||
- Fixed a rare bug in the Microsoft Word export for huge documents.
|
||||
- Fixed a rare bug in the Microsoft Word export for huge documents.
|
||||
- Upgraded dependencies such as Rust, MudBlazor, and others.
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@ -1,42 +1,85 @@
|
||||
|
||||
class MudInput{resetValue(id){const input=document.getElementById(id);if(input){input.value='';}}}
|
||||
class MudPointerEventsNone{constructor(){this.dotnet=null;this.logger=(msg,...args)=>{};this.pointerDownHandlerRef=null;this.pointerUpHandlerRef=null;this.pointerDownMap=new Map();this.pointerUpMap=new Map();}
|
||||
listenForPointerEvents(dotNetReference,elementId,options){if(!options){this.logger("options object is required but was not provided");return;}
|
||||
if(options.enableLogging){this.logger=(msg,...args)=>console.log("[MudBlazor | PointerEventsNone]",msg,...args);}else{this.logger=(msg,...args)=>{};}
|
||||
this.logger("Called listenForPointerEvents",{dotNetReference,elementId,options});if(!dotNetReference){this.logger("dotNetReference is required but was not provided");return;}
|
||||
if(!elementId){this.logger("elementId is required but was not provided");return;}
|
||||
if(!options.subscribeDown&&!options.subscribeUp){this.logger("No subscriptions added: both subscribeDown and subscribeUp are false");return;}
|
||||
if(!this.dotnet){this.dotnet=dotNetReference;}
|
||||
if(options.subscribeDown){this.logger("Subscribing to 'pointerdown' for element:",elementId);this.pointerDownMap.set(elementId,options);if(!this.pointerDownHandlerRef){this.logger("Registering global 'pointerdown' event listener");this.pointerDownHandlerRef=this.pointerDownHandler.bind(this);document.addEventListener("pointerdown",this.pointerDownHandlerRef,false);}}
|
||||
if(options.subscribeUp){this.logger("Subscribing to 'pointerup' events for element:",elementId);this.pointerUpMap.set(elementId,options);if(!this.pointerUpHandlerRef){this.logger("Registering global 'pointerup' event listener");this.pointerUpHandlerRef=this.pointerUpHandler.bind(this);document.addEventListener("pointerup",this.pointerUpHandlerRef,false);}}}
|
||||
pointerDownHandler(event){this._handlePointerEvent(event,this.pointerDownMap,"RaiseOnPointerDown");}
|
||||
pointerUpHandler(event){this._handlePointerEvent(event,this.pointerUpMap,"RaiseOnPointerUp");}
|
||||
_handlePointerEvent(event,map,raiseMethod){if(map.size===0){this.logger("No elements registered for",raiseMethod);return;}
|
||||
const elements=[];for(const id of map.keys()){const element=document.getElementById(id);if(element){elements.push(element);}else{this.logger("Element",id,"not found in DOM");}}
|
||||
if(elements.length===0){this.logger("None of the registered elements were found in the DOM for",raiseMethod);return;}
|
||||
elements.forEach(x=>x.style.pointerEvents="auto");const elementsFromPoint=document.elementsFromPoint(event.clientX,event.clientY);elements.forEach(x=>x.style.pointerEvents="none");const matchingIds=[];for(const element of elementsFromPoint){if(!element.id||!map.has(element.id)){break;}
|
||||
matchingIds.push(element.id);}
|
||||
if(matchingIds.length===0){this.logger("No matching registered elements found under pointer for",raiseMethod);return;}
|
||||
this.logger("Raising",raiseMethod,"for matching element(s):",matchingIds);this.dotnet.invokeMethodAsync(raiseMethod,matchingIds);}
|
||||
cancelListener(elementId){if(!elementId){this.logger("cancelListener called with invalid elementId");return;}
|
||||
const hadDown=this.pointerDownMap.delete(elementId);const hadUp=this.pointerUpMap.delete(elementId);if(hadDown||hadUp){this.logger("Cancelled listener for element",elementId);}else{this.logger("No active listener found for element",elementId);}
|
||||
if(this.pointerDownMap.size===0&&this.pointerDownHandlerRef){this.logger("No more elements listening for 'pointerdown' — removing global event listener");document.removeEventListener("pointerdown",this.pointerDownHandlerRef);this.pointerDownHandlerRef=null;}
|
||||
if(this.pointerUpMap.size===0&&this.pointerUpHandlerRef){this.logger("No more elements listening for 'pointerup' — removing global event listener");document.removeEventListener("pointerup",this.pointerUpHandlerRef);this.pointerUpHandlerRef=null;}}
|
||||
dispose(){if(!this.dotnet&&!this.pointerDownHandlerRef&&!this.pointerUpHandlerRef){this.logger("dispose() called but instance was already cleaned up");return;}
|
||||
this.logger("Disposing");if(this.pointerDownHandlerRef){this.logger("Removing global 'pointerdown' event listener");document.removeEventListener("pointerdown",this.pointerDownHandlerRef);this.pointerDownHandlerRef=null;}
|
||||
if(this.pointerUpHandlerRef){this.logger("Removing global 'pointerup' event listener");document.removeEventListener("pointerup",this.pointerUpHandlerRef);this.pointerUpHandlerRef=null;}
|
||||
const downCount=this.pointerDownMap.size;const upCount=this.pointerUpMap.size;if(downCount>0){this.logger("Clearing",downCount,"element(s) from pointerDownMap");}
|
||||
if(upCount>0){this.logger("Clearing",upCount,"element(s) from pointerUpMap");}
|
||||
this.pointerDownMap.clear();this.pointerUpMap.clear();this.dotnet=null;}}
|
||||
window.mudPointerEventsNone=new MudPointerEventsNone();window.mudTimePicker={initPointerEvents:(clock,dotNetHelper)=>{let isPointerDown=false;const startHandler=(event)=>{if(event.button!==0){return;}
|
||||
isPointerDown=true;event.target.releasePointerCapture(event.pointerId);if(event.target.classList.contains('mud-hour')||event.target.classList.contains('mud-minute')){let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('SelectTimeFromStick',stickValue,false);}
|
||||
event.preventDefault();};const endHandler=(event)=>{if(event.button!==0){return;}
|
||||
isPointerDown=false;if(event.target.classList.contains('mud-hour')||event.target.classList.contains('mud-minute')){let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('OnStickClick',stickValue);}
|
||||
event.preventDefault();};const moveHandler=(event)=>{if(!isPointerDown||(!event.target.classList.contains('mud-hour')&&!event.target.classList.contains('mud-minute'))){return;}
|
||||
let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('SelectTimeFromStick',stickValue,true);event.preventDefault();};clock.addEventListener('pointerdown',startHandler);clock.addEventListener('pointerup',endHandler);clock.addEventListener('pointercancel',endHandler);clock.addEventListener('pointerover',moveHandler);clock.destroy=()=>{clock.removeEventListener('pointerdown',startHandler);clock.removeEventListener('pointerup',endHandler);clock.removeEventListener('pointercancel',endHandler);clock.removeEventListener('pointerover',moveHandler);};},destroyPointerEvents:(container)=>{if(container==null){return;}
|
||||
if(typeof container.destroy==='function'){container.destroy();}}};class MudResizeObserverFactory{constructor(){this._maps={};}
|
||||
connect(id,dotNetRef,elements,elementIds,options){var existingEntry=this._maps[id];if(!existingEntry){var observer=new MudResizeObserver(dotNetRef,options);this._maps[id]=observer;}
|
||||
var result=this._maps[id].connect(elements,elementIds);return result;}
|
||||
disconnect(id,element){var existingEntry=this._maps[id];if(existingEntry){existingEntry.disconnect(element);}}
|
||||
cancelListener(id){var existingEntry=this._maps[id];if(existingEntry){existingEntry.cancelListener();delete this._maps[id];}}}
|
||||
class MudResizeObserver{constructor(dotNetRef,options){this.logger=options.enableLogging?console.log:(message)=>{};this.options=options;this._dotNetRef=dotNetRef
|
||||
var delay=(this.options||{}).reportRate||200;this.throttleResizeHandlerId=-1;var observervedElements=[];this._observervedElements=observervedElements;this.logger('[MudBlazor | ResizeObserver] Observer initialized');this._resizeObserver=new ResizeObserver(entries=>{var changes=[];this.logger('[MudBlazor | ResizeObserver] changes detected');for(let entry of entries){var target=entry.target;var affectedObservedElement=observervedElements.find((x)=>x.element==target);if(affectedObservedElement){var size=entry.target.getBoundingClientRect();if(affectedObservedElement.isInitialized==true){changes.push({id:affectedObservedElement.id,size:size});}
|
||||
else{affectedObservedElement.isInitialized=true;}}}
|
||||
if(changes.length>0){if(this.throttleResizeHandlerId>=0){clearTimeout(this.throttleResizeHandlerId);}
|
||||
this.throttleResizeHandlerId=window.setTimeout(this.resizeHandler.bind(this,changes),delay);}});}
|
||||
resizeHandler(changes){try{this.logger("[MudBlazor | ResizeObserver] OnSizeChanged handler invoked");this._dotNetRef.invokeMethodAsync("OnSizeChanged",changes);}catch(error){this.logger("[MudBlazor | ResizeObserver] Error in OnSizeChanged handler:",{error});}}
|
||||
connect(elements,ids){var result=[];this.logger('[MudBlazor | ResizeObserver] Start observing elements...');for(var i=0;i<elements.length;i++){var newEntry={element:elements[i],id:ids[i],isInitialized:false,};this.logger("[MudBlazor | ResizeObserver] Start observing element:",{newEntry});result.push(elements[i].getBoundingClientRect());this._observervedElements.push(newEntry);this._resizeObserver.observe(elements[i]);}
|
||||
return result;}
|
||||
disconnect(elementId){this.logger('[MudBlazor | ResizeObserver] Try to unobserve element with id',{elementId});var affectedObservedElement=this._observervedElements.find((x)=>x.id==elementId);if(affectedObservedElement){var element=affectedObservedElement.element;this._resizeObserver.unobserve(element);this.logger('[MudBlazor | ResizeObserver] Element found. Ubobserving size changes of element',{element});var index=this._observervedElements.indexOf(affectedObservedElement);this._observervedElements.splice(index,1);}}
|
||||
cancelListener(){this.logger('[MudBlazor | ResizeObserver] Closing ResizeObserver. Detaching all observed elements');this._resizeObserver.disconnect();this._dotNetRef=undefined;}}
|
||||
window.mudResizeObserver=new MudResizeObserverFactory();class MudInput{resetValue(id){const input=document.getElementById(id);if(input){input.value='';}}}
|
||||
window.mudInput=new MudInput();const darkThemeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");window.darkModeChange=()=>{return darkThemeMediaQuery.matches;};function darkModeChangeListener(e){dotNetHelperTheme.invokeMethodAsync('SystemDarkModeChangedAsync',e.matches);}
|
||||
function watchDarkThemeMedia(dotNetHelper){dotNetHelperTheme=dotNetHelper;darkThemeMediaQuery.addEventListener('change',darkModeChangeListener);}
|
||||
function stopWatchingDarkThemeMedia(){darkThemeMediaQuery.removeEventListener('change',darkModeChangeListener);}
|
||||
class MudThrottledEventManager{constructor(){this.mapper={};}
|
||||
subscribe(eventName,elementId,projection,throotleInterval,key,properties,dotnetReference){const handlerRef=this.throttleEventHandler.bind(this,key);let elem=document.getElementById(elementId);if(elem){elem.addEventListener(eventName,handlerRef,false);let projector=null;if(projection){const parts=projection.split('.');let functionPointer=window;let functionReferenceFound=true;if(parts.length==0||parts.length==1){functionPointer=functionPointer[projection];}
|
||||
else{for(let i=0;i<parts.length;i++){functionPointer=functionPointer[parts[i]];if(!functionPointer){functionReferenceFound=false;break;}}}
|
||||
if(functionReferenceFound===true){projector=functionPointer;}}
|
||||
this.mapper[key]={eventName:eventName,handler:handlerRef,delay:throotleInterval,timerId:-1,reference:dotnetReference,elementId:elementId,properties:properties,projection:projector,};}}
|
||||
subscribeGlobal(eventName,throotleInterval,key,properties,dotnetReference){let handlerRef=throotleInterval>0?this.throttleEventHandler.bind(this,key):this.eventHandler.bind(this,key);document.addEventListener(eventName,handlerRef,false);this.mapper[key]={eventName:eventName,handler:handlerRef,delay:throotleInterval,timerId:-1,reference:dotnetReference,elementId:document,properties:properties,projection:null,};}
|
||||
throttleEventHandler(key,event){const entry=this.mapper[key];if(!entry){return;}
|
||||
clearTimeout(entry.timerId);entry.timerId=window.setTimeout(this.eventHandler.bind(this,key,event),entry.delay);}
|
||||
eventHandler(key,event){const entry=this.mapper[key];if(!entry){return;}
|
||||
var elem=document.getElementById(entry.elementId);if(elem!=event.srcElement&&entry.elementId!=document){return;}
|
||||
const eventEntry={};for(var i=0;i<entry.properties.length;i++){const propertyName=entry.properties[i];const propertyValue=event[entry.properties[i]];if(propertyValue==null){eventEntry[propertyName]=propertyValue;}
|
||||
else if(["touchstart","touchmove","touchend","touchcancel"].includes(event.type)&&["touches","changedTouches","targetTouches"].includes(propertyName)){eventEntry[propertyName]=Array.from(propertyValue,touchPoint=>({identifier:touchPoint.identifier,screenX:touchPoint.screenX,screenY:touchPoint.screenY,clientX:touchPoint.clientX,clientY:touchPoint.clientY,pageX:touchPoint.pageX,pageY:touchPoint.pageY,}));}
|
||||
else{eventEntry[propertyName]=propertyValue;}}
|
||||
if(entry.projection){if(typeof entry.projection==="function"){entry.projection.apply(null,[eventEntry,event]);}}
|
||||
entry.reference.invokeMethodAsync('OnEventOccur',key,JSON.stringify(eventEntry));}
|
||||
unsubscribe(key){const entry=this.mapper[key];if(!entry){return;}
|
||||
entry.reference=null;if(document==entry.elementId){document.removeEventListener(entry.eventName,entry.handler,false);}else{const elem=document.getElementById(entry.elementId);if(elem){elem.removeEventListener(entry.eventName,entry.handler,false);}}
|
||||
delete this.mapper[key];}};window.mudThrottledEventManager=new MudThrottledEventManager();window.mudEventProjections={correctOffset:function(eventEntry,event){var target=event.target.getBoundingClientRect();eventEntry.offsetX=event.clientX-target.x;eventEntry.offsetY=event.clientY-target.y;}};window.getTabbableElements=(element)=>{return element.querySelectorAll("a[href]:not([tabindex='-1']),"+"area[href]:not([tabindex='-1']),"+"button:not([disabled]):not([tabindex='-1']),"+"input:not([disabled]):not([tabindex='-1']):not([type='hidden']),"+"select:not([disabled]):not([tabindex='-1']),"+"textarea:not([disabled]):not([tabindex='-1']),"+"iframe:not([tabindex='-1']),"+"details:not([tabindex='-1']),"+"[tabindex]:not([tabindex='-1']),"+"[contentEditable=true]:not([tabindex='-1'])");};window.serializeParameter=(data,spec)=>{if(typeof data=="undefined"||data===null){return null;}
|
||||
if(typeof data==="number"||typeof data==="string"||typeof data=="boolean"){return data;}
|
||||
let res=(Array.isArray(data))?[]:{};if(!spec){spec="*";}
|
||||
for(let i in data){let currentMember=data[i];if(typeof currentMember==='function'||currentMember===null){continue;}
|
||||
let currentMemberSpec;if(spec!="*"){currentMemberSpec=Array.isArray(data)?spec:spec[i];if(!currentMemberSpec){continue;}}else{currentMemberSpec="*"}
|
||||
if(typeof currentMember==='object'){if(Array.isArray(currentMember)||currentMember.length){res[i]=[];for(let j=0;j<currentMember.length;j++){const arrayItem=currentMember[j];if(typeof arrayItem==='object'){res[i].push(this.serializeParameter(arrayItem,currentMemberSpec));}else{res[i].push(arrayItem);}}}else{if(currentMember.length===0){res[i]=[];}else{res[i]=this.serializeParameter(currentMember,currentMemberSpec);}}}else{if(currentMember===Infinity){currentMember="Infinity";}
|
||||
if(currentMember!==null){res[i]=currentMember;}}}
|
||||
return res;};window.mudGetSvgBBox=(svgElement)=>{if(svgElement==null)return null;const bbox=svgElement.getBBox();return{x:bbox.x,y:bbox.y,width:bbox.width,height:bbox.height};};window.mudObserveElementSize=(dotNetReference,element,functionName='OnElementSizeChanged',debounceMillis=200)=>{if(!element)return;let lastNotifiedTime=0;let scheduledCall=null;const throttledNotify=(width,height)=>{const timestamp=Date.now();const timeSinceLast=timestamp-lastNotifiedTime;if(timeSinceLast>=debounceMillis){lastNotifiedTime=timestamp;try{dotNetReference.invokeMethodAsync(functionName,{width,height,timestamp});}
|
||||
catch(error){this.logger("[MudBlazor] Error in mudObserveElementSize:",{error});}}else{if(scheduledCall!==null){clearTimeout(scheduledCall);}
|
||||
scheduledCall=setTimeout(()=>{lastNotifiedTime=Date.now();scheduledCall=null;try{dotNetReference.invokeMethodAsync(functionName,{width,height,timestamp});}
|
||||
catch(error){this.logger("[MudBlazor] Error in mudObserveElementSize:",{error});}},debounceMillis-timeSinceLast);}};const resizeObserver=new ResizeObserver(entries=>{if(element.isConnected===false){return;}
|
||||
let width=element.clientWidth;let height=element.clientHeight;for(const entry of entries){width=entry.contentRect.width;height=entry.contentRect.height;}
|
||||
width=Math.floor(width);height=Math.floor(height);throttledNotify(width,height);});resizeObserver.observe(element);let mutationObserver=null;const parent=element.parentNode;if(parent){mutationObserver=new MutationObserver(mutations=>{for(const mutation of mutations){for(const removedNode of mutation.removedNodes){if(removedNode===element){cleanup();}}}});mutationObserver.observe(parent,{childList:true});}
|
||||
function cleanup(){resizeObserver.disconnect();if(mutationObserver){mutationObserver.disconnect();}
|
||||
if(scheduledCall!==null){clearTimeout(scheduledCall);}}
|
||||
return{width:element.clientWidth,height:element.clientHeight};};function setRippleOffset(event,target){const rect=target.getBoundingClientRect();const x=event.clientX-rect.left-rect.width/2;const y=event.clientY-rect.top-rect.height/2;target.style.setProperty("--mud-ripple-offset-x",`${x}px`);target.style.setProperty("--mud-ripple-offset-y",`${y}px`);}
|
||||
document.addEventListener("click",function(event){const target=event.target.closest(".mud-ripple");if(target){setRippleOffset(event,target);}});class MudJsEventFactory{connect(dotNetRef,elementId,options){if(!elementId)
|
||||
window.mudInputAutoGrow={initAutoGrow:(elem,maxLines)=>{const compStyle=getComputedStyle(elem);const lineHeight=parseFloat(compStyle.getPropertyValue('line-height'));const paddingTop=parseFloat(compStyle.getPropertyValue('padding-top'));let maxHeight=0;elem.updateParameters=function(newMaxLines){if(newMaxLines>0){maxHeight=lineHeight*newMaxLines+paddingTop;}else{maxHeight=0;}}
|
||||
elem.adjustAutoGrowHeight=function(didReflow=false){const scrollTops=[];let curElem=elem;while(curElem&&curElem.parentNode&&curElem.parentNode instanceof Element){if(curElem.parentNode.scrollTop){scrollTops.push([curElem.parentNode,curElem.parentNode.scrollTop]);}
|
||||
curElem=curElem.parentNode;}
|
||||
elem.style.height=0;if(didReflow){elem.style.textAlign=null;}
|
||||
let minHeight=lineHeight*elem.rows+paddingTop;let newHeight=Math.max(minHeight,elem.scrollHeight);let initialOverflowY=elem.style.overflowY;if(maxHeight>0&&newHeight>maxHeight){elem.style.overflowY='auto';newHeight=maxHeight;}else{elem.style.overflowY='hidden';}
|
||||
elem.style.height=newHeight+"px";scrollTops.forEach(([node,scrollTop])=>{node.style.scrollBehavior='auto';node.scrollTop=scrollTop;node.style.scrollBehavior=null;});if(!didReflow&&initialOverflowY!==elem.style.overflowY&&elem.style.overflowY==='hidden'){elem.style.textAlign='end';elem.adjustAutoGrowHeight(true);}}
|
||||
elem.restoreToInitialState=function(){elem.removeEventListener('input',elem.adjustAutoGrowHeight);elem.style.overflowY=null;elem.style.height=null;}
|
||||
elem.addEventListener('input',elem.adjustAutoGrowHeight);window.addEventListener('resize',elem.adjustAutoGrowHeight);elem.updateParameters(maxLines);elem.adjustAutoGrowHeight();},adjustHeight:(elem)=>{if(typeof elem.adjustAutoGrowHeight==='function'){elem.adjustAutoGrowHeight();}},updateParams:(elem,maxLines)=>{if(typeof elem.updateParameters==='function'){elem.updateParameters(maxLines);}
|
||||
if(typeof elem.adjustAutoGrowHeight==='function'){elem.adjustAutoGrowHeight();}},destroy:(elem)=>{if(elem==null){return;}
|
||||
window.removeEventListener('resize',elem.adjustAutoGrowHeight);if(typeof elem.restoreToInitialState==='function'){elem.restoreToInitialState();}}};function setRippleOffset(event,target){const rect=target.getBoundingClientRect();const x=event.clientX-rect.left-rect.width/2;const y=event.clientY-rect.top-rect.height/2;target.style.setProperty("--mud-ripple-offset-x",`${x}px`);target.style.setProperty("--mud-ripple-offset-y",`${y}px`);}
|
||||
document.addEventListener("click",function(event){const target=event.target.closest(".mud-ripple");if(target){setRippleOffset(event,target);}});class MudScrollManager{constructor(){this._lockCount=0;}
|
||||
scrollToYear(elementId,offset){let element=document.getElementById(elementId);if(element){element.parentNode.scrollTop=element.offsetTop-element.parentNode.offsetTop-element.scrollHeight*3;}}
|
||||
scrollToListItem(elementId){let element=document.getElementById(elementId);if(element){let parent=element.parentElement;if(parent){parent.scrollTop=element.offsetTop;}}}
|
||||
scrollTo(selector,left,top,behavior){let element=document.querySelector(selector)||document.documentElement;element.scrollTo({left,top,behavior});}
|
||||
scrollIntoView(selector,behavior){let element=document.querySelector(selector)||document.documentElement;if(element)
|
||||
element.scrollIntoView({behavior,block:'center',inline:'start'});}
|
||||
scrollToBottom(selector,behavior){let element=document.querySelector(selector);if(element){element.scrollTo({top:element.scrollHeight,behavior:behavior});}else{window.scrollTo({top:document.body.scrollHeight,behavior:behavior});}}
|
||||
lockScroll(selector,lockclass){if(this._lockCount===0){const element=document.querySelector(selector)||document.body;const hasScrollBar=window.innerWidth>document.body.clientWidth;const classToAdd=hasScrollBar?lockclass:lockclass+"-no-padding";element.classList.add(classToAdd);}
|
||||
this._lockCount++;}
|
||||
unlockScroll(selector,lockclass){this._lockCount=Math.max(0,this._lockCount-1);if(this._lockCount===0){const element=document.querySelector(selector)||document.body;element.classList.remove(lockclass);element.classList.remove(lockclass+"-no-padding");}}
|
||||
scrollToVirtualizedItem(containerId,itemIndex,itemHeight,targetItemId,behaviorString){const container=document.getElementById(containerId);if(!container){console.warn(`ScrollManager.scrollToVirtualizedItem:Container with id'${containerId}'not found.`);return;}
|
||||
const isScrollable=container.scrollHeight>container.clientHeight||container.scrollWidth>container.clientWidth;const actualContainer=(container===document.documentElement||container===document.body)&&!isScrollable?window:container;requestAnimationFrame(()=>{if(actualContainer===window){actualContainer.scrollTo(0,itemIndex*itemHeight);}else{actualContainer.scrollTop=itemIndex*itemHeight;}
|
||||
requestAnimationFrame(()=>{const targetElement=document.getElementById(targetItemId);if(targetElement){let scrollBehavior=behaviorString==='smooth'?'smooth':'auto';targetElement.scrollIntoView({behavior:scrollBehavior,block:'nearest',inline:'nearest'});}});});}};window.mudScrollManager=new MudScrollManager();class MudWindow{copyToClipboard(text){navigator.clipboard.writeText(text);}
|
||||
changeCssById(id,css){var element=document.getElementById(id);if(element){element.className=css;}}
|
||||
updateStyleProperty(elementId,propertyName,value){const element=document.getElementById(elementId);if(element){element.style.setProperty(propertyName,value);}}
|
||||
changeGlobalCssVariable(name,newValue){document.documentElement.style.setProperty(name,newValue);}
|
||||
open(args){window.open(args);}}
|
||||
window.mudWindow=new MudWindow();class MudJsEventFactory{connect(dotNetRef,elementId,options){if(!elementId)
|
||||
throw"[MudBlazor | JsEvent] elementId: expected element id!";var element=document.getElementById(elementId);if(!element)
|
||||
throw"[MudBlazor | JsEvent] no element found for id: "+elementId;if(!element.mudJsEvent)
|
||||
element.mudJsEvent=new MudJsEvent(dotNetRef,options);element.mudJsEvent.connect(element);}
|
||||
@ -80,7 +123,15 @@ onpaste(self,e){const invoke=self._subscribedEvents["paste"];if(invoke){e.preven
|
||||
const text=clipboardData.getData('text/plain');self._dotNetRef.invokeMethodAsync('OnPaste',text);}}
|
||||
onselect(self,e){const invoke=self._subscribedEvents["select"];if(invoke){const start=e.target.selectionStart;const end=e.target.selectionEnd;if(start===end)
|
||||
return;self._dotNetRef.invokeMethodAsync('OnSelect',start,end);}}}
|
||||
window.mudpopoverHelper={mainContainerClass:null,overflowPadding:24,flipMargin:0,debounce:function(func,wait){let timeout;return function executedFunction(...args){const later=()=>{clearTimeout(timeout);func(...args);};clearTimeout(timeout);timeout=setTimeout(later,wait);};},basePopoverZIndex:parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mud-zindex-popover'))||1200,baseTooltipZIndex:parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mud-zindex-tooltip'))||1600,flipClassReplacements:{'top':{'mud-popover-top-left':'mud-popover-bottom-left','mud-popover-top-center':'mud-popover-bottom-center','mud-popover-top-right':'mud-popover-bottom-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-right',},'left':{'mud-popover-top-left':'mud-popover-top-right','mud-popover-center-left':'mud-popover-center-right','mud-popover-bottom-left':'mud-popover-bottom-right','mud-popover-anchor-center-right':'mud-popover-anchor-center-left','mud-popover-anchor-bottom-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-right':'mud-popover-anchor-top-left',},'right':{'mud-popover-top-right':'mud-popover-top-left','mud-popover-center-right':'mud-popover-center-left','mud-popover-bottom-right':'mud-popover-bottom-left','mud-popover-anchor-center-left':'mud-popover-anchor-center-right','mud-popover-anchor-bottom-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-left':'mud-popover-anchor-top-right',},'bottom':{'mud-popover-bottom-left':'mud-popover-top-left','mud-popover-bottom-center':'mud-popover-top-center','mud-popover-bottom-right':'mud-popover-top-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-right',},'top-and-left':{'mud-popover-top-left':'mud-popover-bottom-right','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right',},'top-and-right':{'mud-popover-top-right':'mud-popover-bottom-left','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left',},'bottom-and-left':{'mud-popover-bottom-left':'mud-popover-top-right','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right',},'bottom-and-right':{'mud-popover-bottom-right':'mud-popover-top-left','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left',},},calculatePopoverPosition:function(list,boundingRect,selfRect){let top=boundingRect.top;let left=boundingRect.left;const isPositionOverride=list.indexOf('mud-popover-position-override')>=0;let offsetX=0;let offsetY=0;if(list.indexOf('mud-popover-top-left')>=0){offsetX=0;offsetY=0;}else if(list.indexOf('mud-popover-top-center')>=0){offsetX=-selfRect.width/2;offsetY=0;}else if(list.indexOf('mud-popover-top-right')>=0){offsetX=-selfRect.width;offsetY=0;}
|
||||
class MudFileUpload{openFilePicker(id){const element=document.getElementById(id);if(!element){return;}
|
||||
try{element.showPicker();}catch(error){element.click();}}}
|
||||
window.mudFileUpload=new MudFileUpload();window.mudDragAndDrop={initDropZone:(id)=>{const elem=document.getElementById(id);elem.addEventListener('dragover',()=>event.preventDefault());elem.addEventListener('dragstart',()=>event.dataTransfer.setData('',event.target.id));},makeDropZonesNotRelative:()=>{var firstDropItems=Array.from(document.getElementsByClassName('mud-drop-item')).filter(x=>x.getAttribute('index')=="-1");for(let dropItem of firstDropItems){dropItem.style.position='static';}
|
||||
const dropZones=document.getElementsByClassName('mud-drop-zone');for(let dropZone of dropZones){dropZone.style.position='unset';}},getDropZoneIdentifierOnPosition:(x,y)=>{const elems=document.elementsFromPoint(x,y);const dropZones=elems.filter(e=>e.classList.contains('mud-drop-zone'))
|
||||
const dropZone=dropZones[0];if(dropZone){return dropZone.getAttribute('identifier')||"";}
|
||||
return"";},getDropIndexOnPosition:(x,y,id)=>{const elems=document.elementsFromPoint(x,y);const dropItems=elems.filter(e=>e.classList.contains('mud-drop-item')&&e.id!=id)
|
||||
const dropItem=dropItems[0];if(dropItem){return dropItem.getAttribute('index')||"";}
|
||||
return"";},makeDropZonesRelative:()=>{const dropZones=document.getElementsByClassName('mud-drop-zone');for(let dropZone of dropZones){dropZone.style.position='relative';}
|
||||
var firstDropItems=Array.from(document.getElementsByClassName('mud-drop-item')).filter(x=>x.getAttribute('index')=="-1");for(let dropItem of firstDropItems){dropItem.style.position='relative';}},moveItemByDifference:(id,dx,dy)=>{const elem=document.getElementById(id);var tx=(parseFloat(elem.getAttribute('data-x'))||0)+dx;var ty=(parseFloat(elem.getAttribute('data-y'))||0)+dy;elem.style.webkitTransform=elem.style.transform='translate3d('+tx+'px, '+ty+'px, 10px)';elem.setAttribute('data-x',tx);elem.setAttribute('data-y',ty);},resetItem:(id)=>{const elem=document.getElementById(id);if(elem){elem.style.webkitTransform=elem.style.transform='';elem.setAttribute('data-x',0);elem.setAttribute('data-y',0);}}};window.mudpopoverHelper={mainContainerClass:null,overflowPadding:24,flipMargin:0,debounce:function(func,wait){let timeout;return function executedFunction(...args){const later=()=>{clearTimeout(timeout);func(...args);};clearTimeout(timeout);timeout=setTimeout(later,wait);};},basePopoverZIndex:parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mud-zindex-popover'))||1200,baseTooltipZIndex:parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mud-zindex-tooltip'))||1600,flipClassReplacements:{'top':{'mud-popover-top-left':'mud-popover-bottom-left','mud-popover-top-center':'mud-popover-bottom-center','mud-popover-top-right':'mud-popover-bottom-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-right',},'left':{'mud-popover-top-left':'mud-popover-top-right','mud-popover-center-left':'mud-popover-center-right','mud-popover-bottom-left':'mud-popover-bottom-right','mud-popover-anchor-center-right':'mud-popover-anchor-center-left','mud-popover-anchor-bottom-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-right':'mud-popover-anchor-top-left',},'right':{'mud-popover-top-right':'mud-popover-top-left','mud-popover-center-right':'mud-popover-center-left','mud-popover-bottom-right':'mud-popover-bottom-left','mud-popover-anchor-center-left':'mud-popover-anchor-center-right','mud-popover-anchor-bottom-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-left':'mud-popover-anchor-top-right',},'bottom':{'mud-popover-bottom-left':'mud-popover-top-left','mud-popover-bottom-center':'mud-popover-top-center','mud-popover-bottom-right':'mud-popover-top-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-right',},'top-and-left':{'mud-popover-top-left':'mud-popover-bottom-right','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right',},'top-and-right':{'mud-popover-top-right':'mud-popover-bottom-left','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left',},'bottom-and-left':{'mud-popover-bottom-left':'mud-popover-top-right','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right',},'bottom-and-right':{'mud-popover-bottom-right':'mud-popover-top-left','mud-popover-anchor-top-left':'mud-popover-anchor-bottom-right','mud-popover-anchor-top-center':'mud-popover-anchor-bottom-center','mud-popover-anchor-top-right':'mud-popover-anchor-bottom-left','mud-popover-anchor-bottom-left':'mud-popover-anchor-top-right','mud-popover-anchor-bottom-center':'mud-popover-anchor-top-center','mud-popover-anchor-bottom-right':'mud-popover-anchor-top-left',},},calculatePopoverPosition:function(list,boundingRect,selfRect){let top=boundingRect.top;let left=boundingRect.left;const isPositionOverride=list.indexOf('mud-popover-position-override')>=0;let offsetX=0;let offsetY=0;if(list.indexOf('mud-popover-top-left')>=0){offsetX=0;offsetY=0;}else if(list.indexOf('mud-popover-top-center')>=0){offsetX=-selfRect.width/2;offsetY=0;}else if(list.indexOf('mud-popover-top-right')>=0){offsetX=-selfRect.width;offsetY=0;}
|
||||
else if(list.indexOf('mud-popover-center-left')>=0){offsetX=0;offsetY=-selfRect.height/2;}else if(list.indexOf('mud-popover-center-center')>=0){offsetX=-selfRect.width/2;offsetY=-selfRect.height/2;}else if(list.indexOf('mud-popover-center-right')>=0){offsetX=-selfRect.width;offsetY=-selfRect.height/2;}
|
||||
else if(list.indexOf('mud-popover-bottom-left')>=0){offsetX=0;offsetY=-selfRect.height;}else if(list.indexOf('mud-popover-bottom-center')>=0){offsetX=-selfRect.width/2;offsetY=-selfRect.height;}else if(list.indexOf('mud-popover-bottom-right')>=0){offsetX=-selfRect.width;offsetY=-selfRect.height;}
|
||||
if(!isPositionOverride){if(list.indexOf('mud-popover-anchor-top-left')>=0){left=boundingRect.left;top=boundingRect.top;}else if(list.indexOf('mud-popover-anchor-top-center')>=0){left=boundingRect.left+boundingRect.width/2;top=boundingRect.top;}else if(list.indexOf('mud-popover-anchor-top-right')>=0){left=boundingRect.left+boundingRect.width;top=boundingRect.top;}else if(list.indexOf('mud-popover-anchor-center-left')>=0){left=boundingRect.left;top=boundingRect.top+boundingRect.height/2;}else if(list.indexOf('mud-popover-anchor-center-center')>=0){left=boundingRect.left+boundingRect.width/2;top=boundingRect.top+boundingRect.height/2;}else if(list.indexOf('mud-popover-anchor-center-right')>=0){left=boundingRect.left+boundingRect.width;top=boundingRect.top+boundingRect.height/2;}else if(list.indexOf('mud-popover-anchor-bottom-left')>=0){left=boundingRect.left;top=boundingRect.top+boundingRect.height;}else if(list.indexOf('mud-popover-anchor-bottom-center')>=0){left=boundingRect.left+boundingRect.width/2;top=boundingRect.top+boundingRect.height;}else if(list.indexOf('mud-popover-anchor-bottom-right')>=0){left=boundingRect.left+boundingRect.width;top=boundingRect.top+boundingRect.height;}}
|
||||
@ -90,7 +141,7 @@ return window.mudpopoverHelper.calculatePopoverPosition(classList,boundingRect,s
|
||||
let current=node.parentNode;while(current&¤t!==document.body){const style=window.getComputedStyle(current);const overflowY=style.overflowY;const overflowX=style.overflowX;const isScrollableY=(overflowY==='auto'||overflowY==='scroll')&¤t.scrollHeight>current.clientHeight;const isScrollableX=(overflowX==='auto'||overflowX==='scroll')&¤t.scrollWidth>current.clientWidth;if(isScrollableY||isScrollableX){return false;}
|
||||
current=current.parentNode;}
|
||||
return true;},placePopover:function(popoverNode,classSelector){if(popoverNode&&popoverNode.parentNode){const id=popoverNode.id.substr(8);const popoverContentNode=document.getElementById('popovercontent-'+id);if(!popoverContentNode)return;const classList=popoverContentNode.classList;if(!classList.contains('mud-popover-open'))return;if(classSelector&&!classList.contains(classSelector))return;let boundingRect=popoverNode.parentNode.getBoundingClientRect();if(!window.mudpopoverHelper.isInViewport(popoverNode,boundingRect)){return;}
|
||||
const selfRect=popoverContentNode.getBoundingClientRect();const popoverNodeStyle=window.getComputedStyle(popoverNode);const isPositionFixed=popoverNodeStyle.position==='fixed';const isPositionOverride=classList.contains('mud-popover-position-override');const isRelativeWidth=classList.contains('mud-popover-relative-width');const isAdaptiveWidth=classList.contains('mud-popover-adaptive-width');const isFlipOnOpen=classList.contains('mud-popover-overflow-flip-onopen');const isFlipAlways=classList.contains('mud-popover-overflow-flip-always');const zIndexAuto=popoverNodeStyle.getPropertyValue('z-index')==='auto';const classListArray=Array.from(classList);if(isPositionOverride){const positiontop=parseInt(popoverContentNode.getAttribute('data-pc-y'))||boundingRect.top;const positionleft=parseInt(popoverContentNode.getAttribute('data-pc-x'))||boundingRect.left;const scrollLeft=window.scrollX;const scrollTop=window.scrollY;boundingRect={left:positionleft-scrollLeft,top:positiontop-scrollTop,right:positionleft+1,bottom:positiontop+1,width:1,height:1};}
|
||||
const selfRect=popoverContentNode.getBoundingClientRect();const popoverNodeStyle=window.getComputedStyle(popoverNode);const isPositionFixed=popoverNodeStyle.position==='fixed';const isPositionOverride=classList.contains('mud-popover-position-override');const isRelativeWidth=classList.contains('mud-popover-relative-width');const isAdaptiveWidth=classList.contains('mud-popover-adaptive-width');const isFlipOnOpen=classList.contains('mud-popover-overflow-flip-onopen');const isFlipAlways=classList.contains('mud-popover-overflow-flip-always');const zIndexAuto=popoverNodeStyle.getPropertyValue('z-index')==='auto';const classListArray=Array.from(classList);if(isPositionOverride){const attrY=popoverContentNode.getAttribute('data-pc-y');const positiontop=attrY==null?boundingRect.top:parseInt(attrY,10);const attrX=popoverContentNode.getAttribute('data-pc-x');const positionleft=attrX==null?boundingRect.left:parseInt(attrX,10);const scrollLeft=window.scrollX;const scrollTop=window.scrollY;boundingRect={left:positionleft-scrollLeft,top:positiontop-scrollTop,right:positionleft+1,bottom:positiontop+1,width:1,height:1};}
|
||||
const position=window.mudpopoverHelper.calculatePopoverPosition(classListArray,boundingRect,selfRect);let left=position.left;let top=position.top;let offsetX=position.offsetX;let offsetY=position.offsetY;let anchorY=position.anchorY;let anchorX=position.anchorX;popoverContentNode.style['max-width']='none';popoverContentNode.style['min-width']='none';if(isRelativeWidth){popoverContentNode.style['max-width']=(boundingRect.width)+'px';}
|
||||
else if(isAdaptiveWidth){popoverContentNode.style['min-width']=(boundingRect.width)+'px';}
|
||||
if(isFlipOnOpen||isFlipAlways){const firstChild=popoverContentNode.firstElementChild;const isList=firstChild&&firstChild.classList&&firstChild.classList.contains("mud-list");if(popoverContentNode.mudHeight&&anchorY>0&&anchorY<window.innerHeight){popoverContentNode.style.maxHeight=null;if(isList){popoverContentNode.mudScrollTop=firstChild.scrollTop;firstChild.style.maxHeight=null;}
|
||||
@ -121,7 +172,7 @@ if(top+offsetY<appBarOffset&&appBarElements.length>0){this.updatePopoverZIndex(p
|
||||
if(isList){const popoverStyle=popoverContentNode.style;const listStyle=firstChild.style;const isUnset=(val)=>val==null||val===''||val==='none';const checkHeight=isUnset(popoverStyle.maxHeight)&&isUnset(listStyle.maxHeight);if(checkHeight){const overflowPadding=window.mudpopoverHelper.overflowPadding;const isCentered=Array.from(classList).some(cls=>cls.includes('mud-popover-anchor-center'));const flipAttr=popoverContentNode.getAttribute('data-mudpopover-flip');const isFlippedUpward=!isCentered&&(flipAttr==='top'||flipAttr==='top-and-left'||flipAttr==='top-and-right');let availableHeight;let shouldClamp=false;if(isFlippedUpward){availableHeight=anchorY-overflowPadding-popoverNode.offsetHeight;shouldClamp=availableHeight<popoverContentNode.offsetHeight;if(shouldClamp){top=overflowPadding;offsetY=0;}}else{const popoverTopEdge=top+offsetY;availableHeight=window.innerHeight-popoverTopEdge-overflowPadding;shouldClamp=popoverContentNode.offsetHeight>availableHeight;}
|
||||
if(shouldClamp){const minVisibleHeight=overflowPadding*3;const newMaxHeight=Math.max(availableHeight,minVisibleHeight);popoverContentNode.style.maxHeight=`${newMaxHeight}px`;firstChild.style.maxHeight=`${newMaxHeight}px`;popoverContentNode.mudHeight="setmaxheight";if(popoverContentNode.mudScrollTop){firstChild.scrollTop=popoverContentNode.mudScrollTop;popoverContentNode.mudScrollTop=null;}}}}}
|
||||
if(isPositionFixed){popoverContentNode.style['position']='fixed';}
|
||||
else if(!classList.contains('mud-popover-fixed')){offsetX+=window.scrollX;offsetY+=window.scrollY}
|
||||
else if(!classList.contains('mud-popover-fixed')){offsetX+=window.scrollX;offsetY+=window.scrollY;}
|
||||
popoverContentNode.style['left']=(left+offsetX)+'px';popoverContentNode.style['top']=(top+offsetY)+'px';this.updatePopoverZIndex(popoverContentNode,popoverNode.parentNode);if(!zIndexAuto){popoverContentNode.style['z-index']=Math.max(popoverNodeStyle.getPropertyValue('z-index'),popoverContentNode.style['z-index']);popoverContentNode.skipZIndex=true;}
|
||||
window.mudpopoverHelper.popoverOverlayUpdates();}
|
||||
else{}},placePopoverByClassSelector:function(classSelector=null){var items=window.mudPopover.getAllObservedContainers();for(let i=0;i<items.length;i++){const popoverNode=document.getElementById('popover-'+items[i]);window.mudpopoverHelper.placePopover(popoverNode,classSelector);}},placePopoverByNode:function(target){const id=target.id.substr(15);const popoverNode=document.getElementById('popover-'+id);window.mudpopoverHelper.placePopover(popoverNode);},countProviders:function(){return document.querySelectorAll(`.${window.mudpopoverHelper.mainContainerClass}`).length;},updatePopoverOverlay:function(popoverContentNode){if(!popoverContentNode||popoverContentNode.classList.contains("mud-tooltip")){return;}
|
||||
@ -181,16 +232,7 @@ window.removeEventListener('resize',this.onResize);window.removeEventListener('s
|
||||
getAllObservedContainers(){return Object.keys(this.map);}}
|
||||
window.mudpopoverHelper.debouncedResize=window.mudpopoverHelper.debounce(()=>{window.mudpopoverHelper.placePopoverByClassSelector();},25);window.mudpopoverHelper.handleScroll=function(node=null){if(node){window.mudpopoverHelper.placePopover(node);}
|
||||
else{window.mudpopoverHelper.placePopoverByClassSelector('mud-popover-fixed');window.mudpopoverHelper.placePopoverByClassSelector('mud-popover-overflow-flip-always');}
|
||||
window.mudpopoverHelper.debouncedResize();};window.mudPopover=new MudPopover();window.mudInputAutoGrow={initAutoGrow:(elem,maxLines)=>{const compStyle=getComputedStyle(elem);const lineHeight=parseFloat(compStyle.getPropertyValue('line-height'));const paddingTop=parseFloat(compStyle.getPropertyValue('padding-top'));let maxHeight=0;elem.updateParameters=function(newMaxLines){if(newMaxLines>0){maxHeight=lineHeight*newMaxLines+paddingTop;}else{maxHeight=0;}}
|
||||
elem.adjustAutoGrowHeight=function(didReflow=false){const scrollTops=[];let curElem=elem;while(curElem&&curElem.parentNode&&curElem.parentNode instanceof Element){if(curElem.parentNode.scrollTop){scrollTops.push([curElem.parentNode,curElem.parentNode.scrollTop]);}
|
||||
curElem=curElem.parentNode;}
|
||||
elem.style.height=0;if(didReflow){elem.style.textAlign=null;}
|
||||
let minHeight=lineHeight*elem.rows+paddingTop;let newHeight=Math.max(minHeight,elem.scrollHeight);let initialOverflowY=elem.style.overflowY;if(maxHeight>0&&newHeight>maxHeight){elem.style.overflowY='auto';newHeight=maxHeight;}else{elem.style.overflowY='hidden';}
|
||||
elem.style.height=newHeight+"px";scrollTops.forEach(([node,scrollTop])=>{node.style.scrollBehavior='auto';node.scrollTop=scrollTop;node.style.scrollBehavior=null;});if(!didReflow&&initialOverflowY!==elem.style.overflowY&&elem.style.overflowY==='hidden'){elem.style.textAlign='end';elem.adjustAutoGrowHeight(true);}}
|
||||
elem.restoreToInitialState=function(){elem.removeEventListener('input',elem.adjustAutoGrowHeight);elem.style.overflowY=null;elem.style.height=null;}
|
||||
elem.addEventListener('input',elem.adjustAutoGrowHeight);window.addEventListener('resize',elem.adjustAutoGrowHeight);elem.updateParameters(maxLines);elem.adjustAutoGrowHeight();},adjustHeight:(elem)=>{if(typeof elem.adjustAutoGrowHeight==='function'){elem.adjustAutoGrowHeight();}},updateParams:(elem,maxLines)=>{if(typeof elem.updateParameters==='function'){elem.updateParameters(maxLines);}
|
||||
if(typeof elem.adjustAutoGrowHeight==='function'){elem.adjustAutoGrowHeight();}},destroy:(elem)=>{if(elem==null){return;}
|
||||
window.removeEventListener('resize',elem.adjustAutoGrowHeight);if(typeof elem.restoreToInitialState==='function'){elem.restoreToInitialState();}}};class MudResizeListener{constructor(id){this.logger=function(message){};this.options={};this.throttleResizeHandlerId=-1;this.dotnet=undefined;this.breakpoint=-1;this.id=id;this.handleResize=this.throttleResizeHandler.bind(this);}
|
||||
window.mudpopoverHelper.debouncedResize();};window.mudPopover=new MudPopover();class MudResizeListener{constructor(id){this.logger=function(message){};this.options={};this.throttleResizeHandlerId=-1;this.dotnet=undefined;this.breakpoint=-1;this.id=id;this.handleResize=this.throttleResizeHandler.bind(this);}
|
||||
listenForResize(dotnetRef,options){if(this.dotnet){this.options=options;return;}
|
||||
this.options=options;this.dotnet=dotnetRef;this.logger=options.enableLogging?console.log:(message)=>{};this.logger(`[MudBlazor]Reporting resize events at rate of:${this.options.reportRate}ms`);window.addEventListener("resize",this.handleResize,false);if(!this.options.suppressInitEvent){this.resizeHandler();}
|
||||
this.breakpoint=this.getBreakpoint(window.innerWidth);}
|
||||
@ -211,22 +253,29 @@ return 1;else
|
||||
return 0;}};window.mudResizeListener=new MudResizeListener();window.mudResizeListenerFactory={mapping:{},listenForResize:(dotnetRef,options,id)=>{var map=window.mudResizeListenerFactory.mapping;if(map[id]){return;}
|
||||
var listener=new MudResizeListener(id);listener.listenForResize(dotnetRef,options);map[id]=listener;},cancelListener:(id)=>{var map=window.mudResizeListenerFactory.mapping;if(!map[id]){return;}
|
||||
var listener=map[id];listener.cancelListener();delete map[id];},cancelListeners:(ids)=>{for(let i=0;i<ids.length;i++){window.mudResizeListenerFactory.cancelListener(ids[i]);}},dispose(){var map=window.mudResizeListenerFactory.mapping;for(var id in map){window.mudResizeListenerFactory.cancelListener(id);}}}
|
||||
class MudScrollListener{constructor(){this.throttleScrollHandlerId=-1;this.handlerRef=null;}
|
||||
class MudThrottledEventManager{constructor(){this.mapper={};}
|
||||
subscribe(eventName,elementId,projection,throotleInterval,key,properties,dotnetReference){const handlerRef=this.throttleEventHandler.bind(this,key);let elem=document.getElementById(elementId);if(elem){elem.addEventListener(eventName,handlerRef,false);let projector=null;if(projection){const parts=projection.split('.');let functionPointer=window;let functionReferenceFound=true;if(parts.length==0||parts.length==1){functionPointer=functionPointer[projection];}
|
||||
else{for(let i=0;i<parts.length;i++){functionPointer=functionPointer[parts[i]];if(!functionPointer){functionReferenceFound=false;break;}}}
|
||||
if(functionReferenceFound===true){projector=functionPointer;}}
|
||||
this.mapper[key]={eventName:eventName,handler:handlerRef,delay:throotleInterval,timerId:-1,reference:dotnetReference,elementId:elementId,properties:properties,projection:projector,};}}
|
||||
subscribeGlobal(eventName,throotleInterval,key,properties,dotnetReference){let handlerRef=throotleInterval>0?this.throttleEventHandler.bind(this,key):this.eventHandler.bind(this,key);document.addEventListener(eventName,handlerRef,false);this.mapper[key]={eventName:eventName,handler:handlerRef,delay:throotleInterval,timerId:-1,reference:dotnetReference,elementId:document,properties:properties,projection:null,};}
|
||||
throttleEventHandler(key,event){const entry=this.mapper[key];if(!entry){return;}
|
||||
clearTimeout(entry.timerId);entry.timerId=window.setTimeout(this.eventHandler.bind(this,key,event),entry.delay);}
|
||||
eventHandler(key,event){const entry=this.mapper[key];if(!entry){return;}
|
||||
var elem=document.getElementById(entry.elementId);if(elem!=event.srcElement&&entry.elementId!=document){return;}
|
||||
const eventEntry={};for(var i=0;i<entry.properties.length;i++){const propertyName=entry.properties[i];const propertyValue=event[entry.properties[i]];if(propertyValue==null){eventEntry[propertyName]=propertyValue;}
|
||||
else if(["touchstart","touchmove","touchend","touchcancel"].includes(event.type)&&["touches","changedTouches","targetTouches"].includes(propertyName)){eventEntry[propertyName]=Array.from(propertyValue,touchPoint=>({identifier:touchPoint.identifier,screenX:touchPoint.screenX,screenY:touchPoint.screenY,clientX:touchPoint.clientX,clientY:touchPoint.clientY,pageX:touchPoint.pageX,pageY:touchPoint.pageY,}));}
|
||||
else{eventEntry[propertyName]=propertyValue;}}
|
||||
if(entry.projection){if(typeof entry.projection==="function"){entry.projection.apply(null,[eventEntry,event]);}}
|
||||
entry.reference.invokeMethodAsync('OnEventOccur',key,JSON.stringify(eventEntry));}
|
||||
unsubscribe(key){const entry=this.mapper[key];if(!entry){return;}
|
||||
entry.reference=null;if(document==entry.elementId){document.removeEventListener(entry.eventName,entry.handler,false);}else{const elem=document.getElementById(entry.elementId);if(elem){elem.removeEventListener(entry.eventName,entry.handler,false);}}
|
||||
delete this.mapper[key];}};window.mudThrottledEventManager=new MudThrottledEventManager();window.mudEventProjections={correctOffset:function(eventEntry,event){var target=event.target.getBoundingClientRect();eventEntry.offsetX=event.clientX-target.x;eventEntry.offsetY=event.clientY-target.y;}};class MudScrollListener{constructor(){this.throttleScrollHandlerId=-1;this.handlerRef=null;}
|
||||
listenForScroll(dotnetReference,selector){let element=selector?document.querySelector(selector):document;this.handlerRef=this.throttleScrollHandler.bind(this,dotnetReference);element.addEventListener('scroll',this.handlerRef,false);}
|
||||
throttleScrollHandler(dotnetReference,event){clearTimeout(this.throttleScrollHandlerId);this.throttleScrollHandlerId=window.setTimeout(this.scrollHandler.bind(this,dotnetReference,event),100);}
|
||||
scrollHandler(dotnetReference,event){try{let element=event.target;let scrollTop=element.scrollTop;let scrollHeight=element.scrollHeight;let scrollWidth=element.scrollWidth;let scrollLeft=element.scrollLeft;let nodeName=element.nodeName;let firstChild=element.firstElementChild;let firstChildBoundingClientRect=firstChild.getBoundingClientRect();dotnetReference.invokeMethodAsync('RaiseOnScroll',{firstChildBoundingClientRect,scrollLeft,scrollTop,scrollHeight,scrollWidth,nodeName,});}catch(error){console.log('[MudBlazor] Error in scrollHandler:',{error});}}
|
||||
cancelListener(selector){let element=selector?document.querySelector(selector):document;element.removeEventListener('scroll',this.handlerRef);}};window.mudScrollListener=new MudScrollListener();class MudWindow{copyToClipboard(text){navigator.clipboard.writeText(text);}
|
||||
changeCssById(id,css){var element=document.getElementById(id);if(element){element.className=css;}}
|
||||
updateStyleProperty(elementId,propertyName,value){const element=document.getElementById(elementId);if(element){element.style.setProperty(propertyName,value);}}
|
||||
changeGlobalCssVariable(name,newValue){document.documentElement.style.setProperty(name,newValue);}
|
||||
open(args){window.open(args);}}
|
||||
window.mudWindow=new MudWindow();window.mudTimePicker={initPointerEvents:(clock,dotNetHelper)=>{let isPointerDown=false;const startHandler=(event)=>{if(event.button!==0){return;}
|
||||
isPointerDown=true;event.target.releasePointerCapture(event.pointerId);if(event.target.classList.contains('mud-hour')||event.target.classList.contains('mud-minute')){let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('SelectTimeFromStick',stickValue,false);}
|
||||
event.preventDefault();};const endHandler=(event)=>{if(event.button!==0){return;}
|
||||
isPointerDown=false;if(event.target.classList.contains('mud-hour')||event.target.classList.contains('mud-minute')){let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('OnStickClick',stickValue);}
|
||||
event.preventDefault();};const moveHandler=(event)=>{if(!isPointerDown||(!event.target.classList.contains('mud-hour')&&!event.target.classList.contains('mud-minute'))){return;}
|
||||
let attributeValue=event.target.getAttribute('data-stick-value');let stickValue=attributeValue?parseInt(attributeValue):-1;dotNetHelper.invokeMethodAsync('SelectTimeFromStick',stickValue,true);event.preventDefault();};clock.addEventListener('pointerdown',startHandler);clock.addEventListener('pointerup',endHandler);clock.addEventListener('pointercancel',endHandler);clock.addEventListener('pointerover',moveHandler);clock.destroy=()=>{clock.removeEventListener('pointerdown',startHandler);clock.removeEventListener('pointerup',endHandler);clock.removeEventListener('pointercancel',endHandler);clock.removeEventListener('pointerover',moveHandler);};},destroyPointerEvents:(container)=>{if(container==null){return;}
|
||||
if(typeof container.destroy==='function'){container.destroy();}}};class MudScrollSpy{constructor(){this.lastKnowElement=null;this.handlerRef=null;}
|
||||
scrollHandler(dotnetReference,event){try{let element=event.target;const isDocument=element===document;const scrollSource=isDocument?(document.scrollingElement||document.documentElement||document.body):element;let scrollTop=scrollSource.scrollTop||0;let scrollHeight=scrollSource.scrollHeight||0;let scrollWidth=scrollSource.scrollWidth||0;let scrollLeft=scrollSource.scrollLeft||0;let nodeName=element.nodeName;let firstChild=element.firstElementChild;let firstChildBoundingClientRect=firstChild.getBoundingClientRect();dotnetReference.invokeMethodAsync('RaiseOnScroll',{firstChildBoundingClientRect,scrollLeft,scrollTop,scrollHeight,scrollWidth,nodeName,});}catch(error){console.error('[MudBlazor] Error in scrollHandler:',{error});}}
|
||||
cancelListener(selector){let element=selector?document.querySelector(selector):document;element.removeEventListener('scroll',this.handlerRef);}};window.mudScrollListener=new MudScrollListener();window.mudTableCell={focusCell(rowId,cellIndex){const row=document.getElementById(rowId);if(!row)return;const cells=row.querySelectorAll('td, th');if(cellIndex>=0&&cellIndex<cells.length){const cell=cells[cellIndex];cell.setAttribute('tabindex','-1');cell.focus();cell.click();}},selectCell(rowId,cellIndex){const row=document.getElementById(rowId);if(!row)return;const cells=row.querySelectorAll('td, th');if(cellIndex>=0&&cellIndex<cells.length){const cell=cells[cellIndex];const input=cell.querySelector('input, textarea');if(input){input.focus();input.select();}}}}
|
||||
class MudScrollSpy{constructor(){this.lastKnowElement=null;this.handlerRef=null;}
|
||||
spying(dotnetReference,containerSelector,sectionClassSelector){this.lastKnowElement=null;this.handlerRef=this.handleScroll.bind(this,dotnetReference,containerSelector,sectionClassSelector);document.addEventListener('scroll',this.handlerRef,true);window.addEventListener('resize',this.handlerRef,true);}
|
||||
handleScroll(dotnetReference,containerSelector,sectionClassSelector,event){const container=document.querySelector(containerSelector);if(container===null){return;}
|
||||
const elements=document.getElementsByClassName(sectionClassSelector);if(elements.length===0){return;}
|
||||
@ -293,18 +342,22 @@ processKeyUp(args,keyOptions){if(this.matchesKeyCombination(keyOptions.preventUp
|
||||
args.preventDefault();if(this.matchesKeyCombination(keyOptions.stopUp,args))
|
||||
args.stopPropagation();}
|
||||
toKeyboardEventArgs(args){return{Key:args.key,Code:args.code,Location:args.location,Repeat:args.repeat,CtrlKey:args.ctrlKey,ShiftKey:args.shiftKey,AltKey:args.altKey,MetaKey:args.metaKey};}}
|
||||
class MudFileUpload{openFilePicker(id){const element=document.getElementById(id);if(!element){return;}
|
||||
try{element.showPicker();}catch(error){element.click();}}}
|
||||
window.mudFileUpload=new MudFileUpload();class MudScrollManager{constructor(){this._lockCount=0;}
|
||||
scrollToYear(elementId,offset){let element=document.getElementById(elementId);if(element){element.parentNode.scrollTop=element.offsetTop-element.parentNode.offsetTop-element.scrollHeight*3;}}
|
||||
scrollToListItem(elementId){let element=document.getElementById(elementId);if(element){let parent=element.parentElement;if(parent){parent.scrollTop=element.offsetTop;}}}
|
||||
scrollTo(selector,left,top,behavior){let element=document.querySelector(selector)||document.documentElement;element.scrollTo({left,top,behavior});}
|
||||
scrollIntoView(selector,behavior){let element=document.querySelector(selector)||document.documentElement;if(element)
|
||||
element.scrollIntoView({behavior,block:'center',inline:'start'});}
|
||||
scrollToBottom(selector,behavior){let element=document.querySelector(selector);if(element){element.scrollTo({top:element.scrollHeight,behavior:behavior});}else{window.scrollTo({top:document.body.scrollHeight,behavior:behavior});}}
|
||||
lockScroll(selector,lockclass){if(this._lockCount===0){const element=document.querySelector(selector)||document.body;const hasScrollBar=window.innerWidth>document.body.clientWidth;const classToAdd=hasScrollBar?lockclass:lockclass+"-no-padding";element.classList.add(classToAdd);}
|
||||
this._lockCount++;}
|
||||
unlockScroll(selector,lockclass){this._lockCount=Math.max(0,this._lockCount-1);if(this._lockCount===0){const element=document.querySelector(selector)||document.body;element.classList.remove(lockclass);element.classList.remove(lockclass+"-no-padding");}}};window.mudScrollManager=new MudScrollManager();class MudElementReference{constructor(){this.listenerId=0;this.eventListeners={};}
|
||||
window.getTabbableElements=(element)=>{return element.querySelectorAll("a[href]:not([tabindex='-1']),"+"area[href]:not([tabindex='-1']),"+"button:not([disabled]):not([tabindex='-1']),"+"input:not([disabled]):not([tabindex='-1']):not([type='hidden']),"+"select:not([disabled]):not([tabindex='-1']),"+"textarea:not([disabled]):not([tabindex='-1']),"+"iframe:not([tabindex='-1']),"+"details:not([tabindex='-1']),"+"[tabindex]:not([tabindex='-1']),"+"[contentEditable=true]:not([tabindex='-1'])");};window.serializeParameter=(data,spec)=>{if(typeof data=="undefined"||data===null){return null;}
|
||||
if(typeof data==="number"||typeof data==="string"||typeof data=="boolean"){return data;}
|
||||
let res=(Array.isArray(data))?[]:{};if(!spec){spec="*";}
|
||||
for(let i in data){let currentMember=data[i];if(typeof currentMember==='function'||currentMember===null){continue;}
|
||||
let currentMemberSpec;if(spec!="*"){currentMemberSpec=Array.isArray(data)?spec:spec[i];if(!currentMemberSpec){continue;}}else{currentMemberSpec="*"}
|
||||
if(typeof currentMember==='object'){if(Array.isArray(currentMember)||currentMember.length){res[i]=[];for(let j=0;j<currentMember.length;j++){const arrayItem=currentMember[j];if(typeof arrayItem==='object'){res[i].push(this.serializeParameter(arrayItem,currentMemberSpec));}else{res[i].push(arrayItem);}}}else{if(currentMember.length===0){res[i]=[];}else{res[i]=this.serializeParameter(currentMember,currentMemberSpec);}}}else{if(currentMember===Infinity){currentMember="Infinity";}
|
||||
if(currentMember!==null){res[i]=currentMember;}}}
|
||||
return res;};window.mudGetSvgBBox=(svgElement)=>{if(svgElement==null)return null;const bbox=svgElement.getBBox();return{x:bbox.x,y:bbox.y,width:bbox.width,height:bbox.height};};window.mudObserveElementSize=(dotNetReference,element,functionName='OnElementSizeChanged',debounceMillis=200)=>{if(!element)return;let lastNotifiedTime=0;let scheduledCall=null;const throttledNotify=(width,height)=>{const timestamp=Date.now();const timeSinceLast=timestamp-lastNotifiedTime;if(timeSinceLast>=debounceMillis){lastNotifiedTime=timestamp;try{dotNetReference.invokeMethodAsync(functionName,{width,height,timestamp});}
|
||||
catch(error){this.logger("[MudBlazor] Error in mudObserveElementSize:",{error});}}else{if(scheduledCall!==null){clearTimeout(scheduledCall);}
|
||||
scheduledCall=setTimeout(()=>{lastNotifiedTime=Date.now();scheduledCall=null;try{dotNetReference.invokeMethodAsync(functionName,{width,height,timestamp});}
|
||||
catch(error){this.logger("[MudBlazor] Error in mudObserveElementSize:",{error});}},debounceMillis-timeSinceLast);}};const resizeObserver=new ResizeObserver(entries=>{if(element.isConnected===false){return;}
|
||||
let width=element.clientWidth;let height=element.clientHeight;for(const entry of entries){width=entry.contentRect.width;height=entry.contentRect.height;}
|
||||
width=Math.floor(width);height=Math.floor(height);throttledNotify(width,height);});resizeObserver.observe(element);let mutationObserver=null;const parent=element.parentNode;if(parent){mutationObserver=new MutationObserver(mutations=>{for(const mutation of mutations){for(const removedNode of mutation.removedNodes){if(removedNode===element){cleanup();}}}});mutationObserver.observe(parent,{childList:true});}
|
||||
function cleanup(){resizeObserver.disconnect();if(mutationObserver){mutationObserver.disconnect();}
|
||||
if(scheduledCall!==null){clearTimeout(scheduledCall);}}
|
||||
return{width:element.clientWidth,height:element.clientHeight};};class MudElementReference{constructor(){this.listenerId=0;this.eventListeners={};}
|
||||
focus(element){if(element)
|
||||
{element.focus();}}
|
||||
blur(element){if(element){element.blur();}}
|
||||
@ -338,53 +391,4 @@ return listeners;}
|
||||
removeDefaultPreventingHandlers(element,eventNames,listenerIds){for(let index=0;index<eventNames.length;++index){const eventName=eventNames[index];const listenerId=listenerIds[index];this.removeDefaultPreventingHandler(element,eventName,listenerId);}}
|
||||
addOnBlurEvent(element,dotNetReference){if(!element)return;element._mudBlurHandler=function(e){if(!element||!document.contains(element)){window.mudElementRef.removeOnBlurEvent(element);return;}
|
||||
e.preventDefault();if(dotNetReference){dotNetReference.invokeMethodAsync('CallOnBlurredAsync').catch(err=>{console.warn("Error invoking CallOnBlurredAsync, possibly disposed:",err);window.mudElementRef.removeOnBlurEvent(element);});}else{console.error("No dotNetReference found for iosKeyboardFocus");}};element.addEventListener('blur',element._mudBlurHandler);}
|
||||
removeOnBlurEvent(element){if(!element)return;if(element._mudBlurHandler){element.removeEventListener('blur',element._mudBlurHandler);delete element._mudBlurHandler;}}};window.mudElementRef=new MudElementReference();class MudPointerEventsNone{constructor(){this.dotnet=null;this.logger=(msg,...args)=>{};this.pointerDownHandlerRef=null;this.pointerUpHandlerRef=null;this.pointerDownMap=new Map();this.pointerUpMap=new Map();}
|
||||
listenForPointerEvents(dotNetReference,elementId,options){if(!options){this.logger("options object is required but was not provided");return;}
|
||||
if(options.enableLogging){this.logger=(msg,...args)=>console.log("[MudBlazor | PointerEventsNone]",msg,...args);}else{this.logger=(msg,...args)=>{};}
|
||||
this.logger("Called listenForPointerEvents",{dotNetReference,elementId,options});if(!dotNetReference){this.logger("dotNetReference is required but was not provided");return;}
|
||||
if(!elementId){this.logger("elementId is required but was not provided");return;}
|
||||
if(!options.subscribeDown&&!options.subscribeUp){this.logger("No subscriptions added: both subscribeDown and subscribeUp are false");return;}
|
||||
if(!this.dotnet){this.dotnet=dotNetReference;}
|
||||
if(options.subscribeDown){this.logger("Subscribing to 'pointerdown' for element:",elementId);this.pointerDownMap.set(elementId,options);if(!this.pointerDownHandlerRef){this.logger("Registering global 'pointerdown' event listener");this.pointerDownHandlerRef=this.pointerDownHandler.bind(this);document.addEventListener("pointerdown",this.pointerDownHandlerRef,false);}}
|
||||
if(options.subscribeUp){this.logger("Subscribing to 'pointerup' events for element:",elementId);this.pointerUpMap.set(elementId,options);if(!this.pointerUpHandlerRef){this.logger("Registering global 'pointerup' event listener");this.pointerUpHandlerRef=this.pointerUpHandler.bind(this);document.addEventListener("pointerup",this.pointerUpHandlerRef,false);}}}
|
||||
pointerDownHandler(event){this._handlePointerEvent(event,this.pointerDownMap,"RaiseOnPointerDown");}
|
||||
pointerUpHandler(event){this._handlePointerEvent(event,this.pointerUpMap,"RaiseOnPointerUp");}
|
||||
_handlePointerEvent(event,map,raiseMethod){if(map.size===0){this.logger("No elements registered for",raiseMethod);return;}
|
||||
const elements=[];for(const id of map.keys()){const element=document.getElementById(id);if(element){elements.push(element);}else{this.logger("Element",id,"not found in DOM");}}
|
||||
if(elements.length===0){this.logger("None of the registered elements were found in the DOM for",raiseMethod);return;}
|
||||
elements.forEach(x=>x.style.pointerEvents="auto");const elementsFromPoint=document.elementsFromPoint(event.clientX,event.clientY);elements.forEach(x=>x.style.pointerEvents="none");const matchingIds=[];for(const element of elementsFromPoint){if(!element.id||!map.has(element.id)){break;}
|
||||
matchingIds.push(element.id);}
|
||||
if(matchingIds.length===0){this.logger("No matching registered elements found under pointer for",raiseMethod);return;}
|
||||
this.logger("Raising",raiseMethod,"for matching element(s):",matchingIds);this.dotnet.invokeMethodAsync(raiseMethod,matchingIds);}
|
||||
cancelListener(elementId){if(!elementId){this.logger("cancelListener called with invalid elementId");return;}
|
||||
const hadDown=this.pointerDownMap.delete(elementId);const hadUp=this.pointerUpMap.delete(elementId);if(hadDown||hadUp){this.logger("Cancelled listener for element",elementId);}else{this.logger("No active listener found for element",elementId);}
|
||||
if(this.pointerDownMap.size===0&&this.pointerDownHandlerRef){this.logger("No more elements listening for 'pointerdown' — removing global event listener");document.removeEventListener("pointerdown",this.pointerDownHandlerRef);this.pointerDownHandlerRef=null;}
|
||||
if(this.pointerUpMap.size===0&&this.pointerUpHandlerRef){this.logger("No more elements listening for 'pointerup' — removing global event listener");document.removeEventListener("pointerup",this.pointerUpHandlerRef);this.pointerUpHandlerRef=null;}}
|
||||
dispose(){if(!this.dotnet&&!this.pointerDownHandlerRef&&!this.pointerUpHandlerRef){this.logger("dispose() called but instance was already cleaned up");return;}
|
||||
this.logger("Disposing");if(this.pointerDownHandlerRef){this.logger("Removing global 'pointerdown' event listener");document.removeEventListener("pointerdown",this.pointerDownHandlerRef);this.pointerDownHandlerRef=null;}
|
||||
if(this.pointerUpHandlerRef){this.logger("Removing global 'pointerup' event listener");document.removeEventListener("pointerup",this.pointerUpHandlerRef);this.pointerUpHandlerRef=null;}
|
||||
const downCount=this.pointerDownMap.size;const upCount=this.pointerUpMap.size;if(downCount>0){this.logger("Clearing",downCount,"element(s) from pointerDownMap");}
|
||||
if(upCount>0){this.logger("Clearing",upCount,"element(s) from pointerUpMap");}
|
||||
this.pointerDownMap.clear();this.pointerUpMap.clear();this.dotnet=null;}}
|
||||
window.mudPointerEventsNone=new MudPointerEventsNone();class MudResizeObserverFactory{constructor(){this._maps={};}
|
||||
connect(id,dotNetRef,elements,elementIds,options){var existingEntry=this._maps[id];if(!existingEntry){var observer=new MudResizeObserver(dotNetRef,options);this._maps[id]=observer;}
|
||||
var result=this._maps[id].connect(elements,elementIds);return result;}
|
||||
disconnect(id,element){var existingEntry=this._maps[id];if(existingEntry){existingEntry.disconnect(element);}}
|
||||
cancelListener(id){var existingEntry=this._maps[id];if(existingEntry){existingEntry.cancelListener();delete this._maps[id];}}}
|
||||
class MudResizeObserver{constructor(dotNetRef,options){this.logger=options.enableLogging?console.log:(message)=>{};this.options=options;this._dotNetRef=dotNetRef
|
||||
var delay=(this.options||{}).reportRate||200;this.throttleResizeHandlerId=-1;var observervedElements=[];this._observervedElements=observervedElements;this.logger('[MudBlazor | ResizeObserver] Observer initialized');this._resizeObserver=new ResizeObserver(entries=>{var changes=[];this.logger('[MudBlazor | ResizeObserver] changes detected');for(let entry of entries){var target=entry.target;var affectedObservedElement=observervedElements.find((x)=>x.element==target);if(affectedObservedElement){var size=entry.target.getBoundingClientRect();if(affectedObservedElement.isInitialized==true){changes.push({id:affectedObservedElement.id,size:size});}
|
||||
else{affectedObservedElement.isInitialized=true;}}}
|
||||
if(changes.length>0){if(this.throttleResizeHandlerId>=0){clearTimeout(this.throttleResizeHandlerId);}
|
||||
this.throttleResizeHandlerId=window.setTimeout(this.resizeHandler.bind(this,changes),delay);}});}
|
||||
resizeHandler(changes){try{this.logger("[MudBlazor | ResizeObserver] OnSizeChanged handler invoked");this._dotNetRef.invokeMethodAsync("OnSizeChanged",changes);}catch(error){this.logger("[MudBlazor | ResizeObserver] Error in OnSizeChanged handler:",{error});}}
|
||||
connect(elements,ids){var result=[];this.logger('[MudBlazor | ResizeObserver] Start observing elements...');for(var i=0;i<elements.length;i++){var newEntry={element:elements[i],id:ids[i],isInitialized:false,};this.logger("[MudBlazor | ResizeObserver] Start observing element:",{newEntry});result.push(elements[i].getBoundingClientRect());this._observervedElements.push(newEntry);this._resizeObserver.observe(elements[i]);}
|
||||
return result;}
|
||||
disconnect(elementId){this.logger('[MudBlazor | ResizeObserver] Try to unobserve element with id',{elementId});var affectedObservedElement=this._observervedElements.find((x)=>x.id==elementId);if(affectedObservedElement){var element=affectedObservedElement.element;this._resizeObserver.unobserve(element);this.logger('[MudBlazor | ResizeObserver] Element found. Ubobserving size changes of element',{element});var index=this._observervedElements.indexOf(affectedObservedElement);this._observervedElements.splice(index,1);}}
|
||||
cancelListener(){this.logger('[MudBlazor | ResizeObserver] Closing ResizeObserver. Detaching all observed elements');this._resizeObserver.disconnect();this._dotNetRef=undefined;}}
|
||||
window.mudResizeObserver=new MudResizeObserverFactory();window.mudDragAndDrop={initDropZone:(id)=>{const elem=document.getElementById(id);elem.addEventListener('dragover',()=>event.preventDefault());elem.addEventListener('dragstart',()=>event.dataTransfer.setData('',event.target.id));},makeDropZonesNotRelative:()=>{var firstDropItems=Array.from(document.getElementsByClassName('mud-drop-item')).filter(x=>x.getAttribute('index')=="-1");for(let dropItem of firstDropItems){dropItem.style.position='static';}
|
||||
const dropZones=document.getElementsByClassName('mud-drop-zone');for(let dropZone of dropZones){dropZone.style.position='unset';}},getDropZoneIdentifierOnPosition:(x,y)=>{const elems=document.elementsFromPoint(x,y);const dropZones=elems.filter(e=>e.classList.contains('mud-drop-zone'))
|
||||
const dropZone=dropZones[0];if(dropZone){return dropZone.getAttribute('identifier')||"";}
|
||||
return"";},getDropIndexOnPosition:(x,y,id)=>{const elems=document.elementsFromPoint(x,y);const dropItems=elems.filter(e=>e.classList.contains('mud-drop-item')&&e.id!=id)
|
||||
const dropItem=dropItems[0];if(dropItem){return dropItem.getAttribute('index')||"";}
|
||||
return"";},makeDropZonesRelative:()=>{const dropZones=document.getElementsByClassName('mud-drop-zone');for(let dropZone of dropZones){dropZone.style.position='relative';}
|
||||
var firstDropItems=Array.from(document.getElementsByClassName('mud-drop-item')).filter(x=>x.getAttribute('index')=="-1");for(let dropItem of firstDropItems){dropItem.style.position='relative';}},moveItemByDifference:(id,dx,dy)=>{const elem=document.getElementById(id);var tx=(parseFloat(elem.getAttribute('data-x'))||0)+dx;var ty=(parseFloat(elem.getAttribute('data-y'))||0)+dy;elem.style.webkitTransform=elem.style.transform='translate3d('+tx+'px, '+ty+'px, 10px)';elem.setAttribute('data-x',tx);elem.setAttribute('data-y',ty);},resetItem:(id)=>{const elem=document.getElementById(id);if(elem){elem.style.webkitTransform=elem.style.transform='';elem.setAttribute('data-x',0);elem.setAttribute('data-y',0);}}};
|
||||
removeOnBlurEvent(element){if(!element)return;if(element._mudBlurHandler){element.removeEventListener('blur',element._mudBlurHandler);delete element._mudBlurHandler;}}};window.mudElementRef=new MudElementReference();
|
||||
Binary file not shown.
Binary file not shown.
@ -3,9 +3,9 @@
|
||||
229
|
||||
9.0.112 (commit 49aa03442a)
|
||||
9.0.11 (commit fa7cdded37)
|
||||
1.91.1 (commit ed61e7d7e)
|
||||
8.12.0
|
||||
1.92.0 (commit ded5c06cf)
|
||||
8.15.0
|
||||
1.8.1
|
||||
009bb33d839, release
|
||||
osx-arm64
|
||||
137.0.7215.0
|
||||
137.0.7215.0
|
||||
|
||||
@ -561,6 +561,56 @@ pub fn select_file(_token: APIToken, payload: Json<SelectFileOptions>) -> Json<F
|
||||
}
|
||||
}
|
||||
|
||||
/// Let the user select some files.
|
||||
#[post("/select/files", data = "<payload>")]
|
||||
pub fn select_files(_token: APIToken, payload: Json<SelectFileOptions>) -> Json<FilesSelectionResponse> {
|
||||
|
||||
// Create a new file dialog builder:
|
||||
let file_dialog = FileDialogBuilder::new();
|
||||
|
||||
// Set the title of the file dialog:
|
||||
let file_dialog = file_dialog.set_title(&payload.title);
|
||||
|
||||
// Set the file type filter if provided:
|
||||
let file_dialog = match &payload.filter {
|
||||
Some(filter) => {
|
||||
file_dialog.add_filter(&filter.filter_name, &filter.filter_extensions.iter().map(|s| s.as_str()).collect::<Vec<&str>>())
|
||||
},
|
||||
|
||||
None => file_dialog,
|
||||
};
|
||||
|
||||
// Set the previous file path if provided:
|
||||
let file_dialog = match &payload.previous_file {
|
||||
Some(previous) => {
|
||||
let previous_path = previous.file_path.as_str();
|
||||
file_dialog.set_directory(previous_path)
|
||||
},
|
||||
|
||||
None => file_dialog,
|
||||
};
|
||||
|
||||
// Show the file dialog and get the selected file path:
|
||||
let file_paths = file_dialog.pick_files();
|
||||
match file_paths {
|
||||
Some(paths) => {
|
||||
info!("User selected {} files.", paths.len());
|
||||
Json(FilesSelectionResponse {
|
||||
user_cancelled: false,
|
||||
selected_file_paths: paths.iter().map(|p| p.to_str().unwrap().to_string()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
None => {
|
||||
info!("User cancelled file selection.");
|
||||
Json(FilesSelectionResponse {
|
||||
user_cancelled: true,
|
||||
selected_file_paths: Vec::new(),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/save/file", data = "<payload>")]
|
||||
pub fn save_file(_token: APIToken, payload: Json<SaveFileOptions>) -> Json<FileSaveResponse> {
|
||||
|
||||
@ -620,6 +670,13 @@ pub struct FileSelectionResponse {
|
||||
user_cancelled: bool,
|
||||
selected_file_path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FilesSelectionResponse {
|
||||
user_cancelled: bool,
|
||||
selected_file_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FileSaveResponse {
|
||||
user_cancelled: bool,
|
||||
|
||||
@ -73,6 +73,7 @@ pub fn start_runtime_api() {
|
||||
crate::app_window::install_update,
|
||||
crate::app_window::select_directory,
|
||||
crate::app_window::select_file,
|
||||
crate::app_window::select_files,
|
||||
crate::app_window::save_file,
|
||||
crate::secret::get_secret,
|
||||
crate::secret::store_secret,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user