mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-07 09:29:40 +00:00
First review
This commit is contained in:
1 parent
0e63f93696
commit
ad6b700978
10 files changed
+108
-99
No files matched your search
@@ -6,10 +6,12 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
|
||||
public static class ConfigurationImportFields
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationImportFields).Namespace, nameof(ConfigurationImportFields));
|
||||
|
||||
public static void ValidateExportId(LuaTable table)
|
||||
{
|
||||
if (!Guid.TryParse(String(table, "Id"), out _))
|
||||
throw new FormatException("The exported item has an invalid ID.");
|
||||
throw new FormatException(TB("The exported item has an invalid ID."));
|
||||
}
|
||||
|
||||
public static string String(LuaTable table, string name, bool required = true)
|
||||
@@ -18,18 +20,18 @@ public static class ConfigurationImportFields
|
||||
{
|
||||
if (value.Type is LuaValueType.String && value.TryRead<string>(out var text))
|
||||
return text;
|
||||
throw new FormatException($"The '{name}' field must be a string.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must be a string."), name));
|
||||
}
|
||||
if (!required)
|
||||
return string.Empty;
|
||||
throw new FormatException($"The '{name}' field must be a string.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must be a string."), name));
|
||||
}
|
||||
|
||||
public static LuaTable Table(LuaTable table, string name)
|
||||
{
|
||||
if (table.TryGetValue(name, out var value) && value.Type is LuaValueType.Table && value.TryRead<LuaTable>(out var nested))
|
||||
return nested;
|
||||
throw new FormatException($"The '{name}' field must be a table.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must be a table."), name));
|
||||
}
|
||||
|
||||
public static T Enum<T>(LuaTable table, string name) where T : struct, Enum
|
||||
@@ -37,7 +39,7 @@ public static class ConfigurationImportFields
|
||||
var text = String(table, name);
|
||||
if (System.Enum.TryParse<T>(text, true, out var result) && System.Enum.IsDefined(result))
|
||||
return result;
|
||||
throw new FormatException($"The '{name}' field has an unknown value.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field has an unknown value."), name));
|
||||
}
|
||||
|
||||
public static bool Bool(LuaTable table, string name, bool fallback = false)
|
||||
@@ -46,7 +48,7 @@ public static class ConfigurationImportFields
|
||||
return fallback;
|
||||
if (value.Type is LuaValueType.Boolean && value.TryRead<bool>(out var result))
|
||||
return result;
|
||||
throw new FormatException($"The '{name}' field must be true or false.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must be true or false."), name));
|
||||
}
|
||||
|
||||
public static int Int(LuaTable table, string name, int fallback = 0)
|
||||
@@ -55,7 +57,7 @@ public static class ConfigurationImportFields
|
||||
return fallback;
|
||||
if (value.Type is LuaValueType.Number && value.TryRead<double>(out var number) && number >= int.MinValue && number <= int.MaxValue && number == Math.Truncate(number))
|
||||
return (int)number;
|
||||
throw new FormatException($"The '{name}' field must be a whole number.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must be a whole number."), name));
|
||||
}
|
||||
|
||||
public static List<string> Strings(LuaTable table, string name)
|
||||
@@ -65,7 +67,7 @@ public static class ConfigurationImportFields
|
||||
for (var i = 1; i <= nested.ArrayLength; i++)
|
||||
{
|
||||
if (nested[i].Type is not LuaValueType.String || !nested[i].TryRead<string>(out var text) || string.IsNullOrWhiteSpace(text))
|
||||
throw new FormatException($"The '{name}' field contains an invalid entry.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field contains an invalid entry."), name));
|
||||
result.Add(text);
|
||||
}
|
||||
return result;
|
||||
@@ -73,6 +75,19 @@ public static class ConfigurationImportFields
|
||||
|
||||
public static bool IsExistingLocalFile(string path) => Path.IsPathFullyQualified(path) && File.Exists(path);
|
||||
|
||||
/// <summary>Lists the given references as a warning, or returns an empty text when nothing is missing.</summary>
|
||||
public static string UnavailableReferencesIssue(IReadOnlyCollection<string> missing) => missing.Count == 0
|
||||
? string.Empty
|
||||
: string.Format(TB("Unavailable references: {0}. Review the selections before saving."), string.Join(", ", missing));
|
||||
|
||||
public static string MissingProviderReference(string id) => string.Format(TB("provider {0}"), id);
|
||||
|
||||
public static string MissingProfileReference(string id) => string.Format(TB("profile {0}"), id);
|
||||
|
||||
public static string MissingToolReference(string id) => string.Format(TB("tool {0}"), id);
|
||||
|
||||
public static string MissingDataSourceReference(string id) => string.Format(TB("data source {0}"), id);
|
||||
|
||||
public static string Credential(LuaTable table, string name, out string issue, EnterpriseEncryption? decryptionService = null)
|
||||
{
|
||||
issue = string.Empty;
|
||||
@@ -80,11 +95,11 @@ public static class ConfigurationImportFields
|
||||
if (string.IsNullOrEmpty(encrypted))
|
||||
return string.Empty;
|
||||
if (!EnterpriseEncryption.IsEncrypted(encrypted))
|
||||
throw new FormatException($"The '{name}' field must contain an ENC:v1 credential.");
|
||||
throw new FormatException(string.Format(TB("The '{0}' field must contain an ENC:v1 credential."), name));
|
||||
var encryption = decryptionService ?? PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true && encryption.TryDecrypt(encrypted, out var decrypted))
|
||||
return decrypted;
|
||||
issue = "The embedded credential could not be decrypted on this device. Enter your own credential before saving.";
|
||||
issue = TB("The embedded credential could not be decrypted on this device. Enter your own credential before saving.");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace AIStudio.Tools.PluginSystem;
|
||||
/// <summary>Checks the fields used by the creation forms before leaving the paste dialog.</summary>
|
||||
public static class ConfigurationSnippetImportValidation
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetImportValidation).Namespace, nameof(ConfigurationSnippetImportValidation));
|
||||
|
||||
public static void Validate(string section, LuaTable table)
|
||||
{
|
||||
ConfigurationImportFields.ValidateExportId(table);
|
||||
@@ -47,7 +49,7 @@ public static class ConfigurationSnippetImportValidation
|
||||
ConfigurationImportFields.Bool(table, "HidePolicyDefinition");
|
||||
break;
|
||||
default:
|
||||
throw new FormatException("This configuration section cannot be imported here.");
|
||||
throw new FormatException(TB("This configuration section cannot be imported here."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +86,10 @@ public static class ConfigurationSnippetImportValidation
|
||||
for (var index = 1; index <= messages.ArrayLength; index++)
|
||||
{
|
||||
if (messages[index].Type is not LuaValueType.Table || !messages[index].TryRead<LuaTable>(out var message))
|
||||
throw new FormatException("An example conversation entry is not a table.");
|
||||
throw new FormatException(TB("An example conversation entry is not a table."));
|
||||
ConfigurationImportFields.Enum<ChatRole>(message, "Role");
|
||||
if (string.IsNullOrWhiteSpace(ConfigurationImportFields.String(message, "Content")))
|
||||
throw new FormatException("An example conversation message is empty.");
|
||||
throw new FormatException(TB("An example conversation message is empty."));
|
||||
}
|
||||
if (table.TryGetValue("ToolIds", out _))
|
||||
ConfigurationImportFields.Strings(table, "ToolIds");
|
||||
@@ -101,27 +103,27 @@ public static class ConfigurationSnippetImportValidation
|
||||
ConfigurationImportFields.Strings(options, "PreselectedDataSourceIds");
|
||||
}
|
||||
if (!ChatTemplate.TryParseChatTemplateTable(0, table, Guid.Empty, string.Empty, out _))
|
||||
throw new FormatException("The chat template fields are malformed.");
|
||||
throw new FormatException(TB("The chat template fields are malformed."));
|
||||
ConfigurationImportFields.Strings(table, "FileAttachments");
|
||||
}
|
||||
|
||||
private static void ValidateERIDataSource(LuaTable table)
|
||||
{
|
||||
if (ConfigurationImportFields.String(table, "Type") != "ERI_V1")
|
||||
throw new FormatException("This data source is not an ERI v1 data source.");
|
||||
throw new FormatException(TB("This data source is not an ERI v1 data source."));
|
||||
ConfigurationImportFields.String(table, "Name");
|
||||
ConfigurationImportFields.String(table, "Hostname");
|
||||
var port = ConfigurationImportFields.Int(table, "Port");
|
||||
if (port is < 1 or > 65535)
|
||||
throw new FormatException("The 'Port' field must be between 1 and 65535.");
|
||||
throw new FormatException(TB("The 'Port' field must be between 1 and 65535."));
|
||||
var authMethod = ConfigurationImportFields.Enum<AuthMethod>(table, "AuthMethod");
|
||||
if (authMethod is AuthMethod.KERBEROS)
|
||||
throw new FormatException("Kerberos data sources cannot be imported from configuration snippets.");
|
||||
throw new FormatException(TB("Kerberos data sources cannot be imported from configuration snippets."));
|
||||
ConfigurationImportFields.Enum<DataSourceSecurity>(table, "SecurityPolicy");
|
||||
ConfigurationImportFields.String(table, "SelectedRetrievalId");
|
||||
var maxMatches = ConfigurationImportFields.Int(table, "MaxMatches", 10);
|
||||
if (maxMatches is < 1 or > ushort.MaxValue)
|
||||
throw new FormatException("The 'MaxMatches' field is outside the allowed range.");
|
||||
throw new FormatException(TB("The 'MaxMatches' field is outside the allowed range."));
|
||||
var secretName = authMethod switch
|
||||
{
|
||||
AuthMethod.TOKEN => "Token",
|
||||
|
||||
@@ -13,13 +13,15 @@ public sealed class ConfigurationSnippetParser
|
||||
|
||||
private ConfigurationSnippetParser(string source) => this.source = source;
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationSnippetParser).Namespace, nameof(ConfigurationSnippetParser));
|
||||
|
||||
public static bool TryParse(string source, string expectedSection, out LuaTable table, out string issue)
|
||||
{
|
||||
table = new LuaTable();
|
||||
issue = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(source) || source.Length > 1_000_000)
|
||||
{
|
||||
issue = "Paste one exported configuration snippet (up to 1 MB).";
|
||||
issue = TB("Paste one exported configuration snippet (up to 1 MB).");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,13 +31,13 @@ public sealed class ConfigurationSnippetParser
|
||||
parser.ExpectWord("CONFIG");
|
||||
var section = parser.ReadBracketedString();
|
||||
if (section != expectedSection)
|
||||
throw new FormatException($"This is a {section} snippet. Paste a {expectedSection} snippet here.");
|
||||
throw new FormatException(string.Format(TB("This is a {0} snippet. Paste a {1} snippet here."), section, expectedSection));
|
||||
|
||||
parser.Expect('[');
|
||||
parser.Expect('#');
|
||||
parser.ExpectWord("CONFIG");
|
||||
if (parser.ReadBracketedString() != expectedSection)
|
||||
throw new FormatException("The configuration section names do not match.");
|
||||
throw new FormatException(TB("The configuration section names do not match."));
|
||||
parser.Expect('+');
|
||||
parser.Expect('1');
|
||||
parser.Expect(']');
|
||||
@@ -43,7 +45,7 @@ public sealed class ConfigurationSnippetParser
|
||||
table = parser.ReadTable(0);
|
||||
parser.SkipTrivia();
|
||||
if (parser.position != source.Length)
|
||||
throw new FormatException("The snippet must contain exactly one table assignment and no executable code.");
|
||||
throw new FormatException(TB("The snippet must contain exactly one table assignment and no executable code."));
|
||||
return true;
|
||||
}
|
||||
catch (FormatException exception)
|
||||
@@ -56,7 +58,7 @@ public sealed class ConfigurationSnippetParser
|
||||
private LuaTable ReadTable(int depth)
|
||||
{
|
||||
if (depth > 32)
|
||||
throw new FormatException("The snippet contains too many nested tables.");
|
||||
throw new FormatException(TB("The snippet contains too many nested tables."));
|
||||
this.Expect('{');
|
||||
var table = new LuaTable();
|
||||
var arrayIndex = 1;
|
||||
@@ -67,13 +69,15 @@ public sealed class ConfigurationSnippetParser
|
||||
if (this.Take('}'))
|
||||
return table;
|
||||
|
||||
if (this.Take('['))
|
||||
// "[[" or "[=" opens a long string, which is an array value rather than a bracketed key:
|
||||
if (this.Peek() == '[' && !this.IsLongStringStart())
|
||||
{
|
||||
this.position++;
|
||||
var key = this.ReadString();
|
||||
this.Expect(']');
|
||||
this.Expect('=');
|
||||
if (!names.Add(key))
|
||||
throw new FormatException($"The field '{key}' occurs more than once.");
|
||||
throw new FormatException(string.Format(TB("The field '{0}' occurs more than once."), key));
|
||||
table[key] = this.ReadValue(depth + 1);
|
||||
}
|
||||
else
|
||||
@@ -83,7 +87,7 @@ public sealed class ConfigurationSnippetParser
|
||||
if (this.Take('}'))
|
||||
return table;
|
||||
if (!this.Take(',') && !this.Take(';'))
|
||||
throw new FormatException($"Expected a comma or closing brace at character {this.position + 1}.");
|
||||
throw new FormatException(string.Format(TB("Expected a comma or closing brace at character {0}."), this.position + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +118,11 @@ public sealed class ConfigurationSnippetParser
|
||||
}
|
||||
if (this.position > start && double.TryParse(this.source[start..this.position], NumberStyles.Float, CultureInfo.InvariantCulture, out var number) && double.IsFinite(number))
|
||||
return number;
|
||||
throw new FormatException($"Only literal values are allowed at character {start + 1}; executable Lua is not accepted.");
|
||||
throw new FormatException(string.Format(TB("Only literal values are allowed at character {0}; executable Lua is not accepted."), start + 1));
|
||||
}
|
||||
|
||||
private bool IsLongStringStart() => this.position + 1 < this.source.Length && this.source[this.position + 1] is '[' or '=';
|
||||
|
||||
private string ReadBracketedString()
|
||||
{
|
||||
this.Expect('[');
|
||||
@@ -136,7 +142,9 @@ public sealed class ConfigurationSnippetParser
|
||||
while (this.Peek() == '=')
|
||||
this.position++;
|
||||
var equals = this.source[equalsStart..this.position];
|
||||
this.Expect('[');
|
||||
if (this.Peek() != '[')
|
||||
throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), '[', this.position + 1));
|
||||
this.position++;
|
||||
if (this.Peek() is '\r' or '\n')
|
||||
{
|
||||
if (this.Take('\r'))
|
||||
@@ -146,13 +154,13 @@ public sealed class ConfigurationSnippetParser
|
||||
}
|
||||
var end = this.source.IndexOf("]" + equals + "]", this.position, StringComparison.Ordinal);
|
||||
if (end < 0)
|
||||
throw new FormatException("Unterminated long string.");
|
||||
throw new FormatException(TB("Unterminated long string."));
|
||||
var value = this.source[this.position..end];
|
||||
this.position = end + equals.Length + 2;
|
||||
return value;
|
||||
}
|
||||
if (quote is not ('"' or '\''))
|
||||
throw new FormatException($"Expected a quoted string at character {this.position + 1}.");
|
||||
throw new FormatException(string.Format(TB("Expected a quoted string at character {0}."), this.position + 1));
|
||||
this.position++;
|
||||
var builder = new StringBuilder();
|
||||
while (this.position < this.source.Length)
|
||||
@@ -161,7 +169,7 @@ public sealed class ConfigurationSnippetParser
|
||||
if (c == quote)
|
||||
return builder.ToString();
|
||||
if (c is '\r' or '\n')
|
||||
throw new FormatException("A quoted string contains an unescaped newline.");
|
||||
throw new FormatException(TB("A quoted string contains an unescaped newline."));
|
||||
if (c != '\\')
|
||||
{
|
||||
builder.Append(c);
|
||||
@@ -174,10 +182,10 @@ public sealed class ConfigurationSnippetParser
|
||||
{
|
||||
'n' => '\n', 'r' => '\r', 't' => '\t', 'a' => '\a', 'b' => '\b', 'f' => '\f', 'v' => '\v',
|
||||
'\\' => '\\', '"' => '"', '\'' => '\'',
|
||||
_ => throw new FormatException($"Unsupported string escape \\{c}."),
|
||||
_ => throw new FormatException(string.Format(TB("Unsupported string escape sequence: {0}"), "\\" + c)),
|
||||
});
|
||||
}
|
||||
throw new FormatException("Unterminated quoted string.");
|
||||
throw new FormatException(TB("Unterminated quoted string."));
|
||||
}
|
||||
|
||||
private void SkipTrivia()
|
||||
@@ -214,7 +222,7 @@ public sealed class ConfigurationSnippetParser
|
||||
private void Expect(char c)
|
||||
{
|
||||
if (!this.Take(c))
|
||||
throw new FormatException($"Expected '{c}' at character {this.position + 1}.");
|
||||
throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), c, this.position + 1));
|
||||
}
|
||||
|
||||
private bool TakeWord(string word)
|
||||
@@ -230,6 +238,6 @@ public sealed class ConfigurationSnippetParser
|
||||
private void ExpectWord(string word)
|
||||
{
|
||||
if (!this.TakeWord(word))
|
||||
throw new FormatException($"Expected '{word}' at character {this.position + 1}.");
|
||||
throw new FormatException(string.Format(TB("Expected '{0}' at character {1}."), word, this.position + 1));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user