mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-13 12:47:21 +00:00
Added an analyzer which keeps line breaks LF on every system (#1193)
This commit is contained in:
1 parent
b1c1f13993
commit
65d50de033
96 files changed
+741
-393
No files matched your search
@@ -29,6 +29,7 @@ Key structure:
|
|||||||
|
|
||||||
- **Naming conventions** - Constants, enum members, and `static readonly` fields use `UPPER_SNAKE_CASE` such as `MY_CONSTANT`.
|
- **Naming conventions** - Constants, enum members, and `static readonly` fields use `UPPER_SNAKE_CASE` such as `MY_CONSTANT`.
|
||||||
- **Extension members** - Declare extension methods in an `extension` block of their static class, one block per receiver, never with a `this` parameter: MWAIS0016 refuses the classic form. The `<param>` of the receiver, and of a generic block its `<typeparam>`, belongs on the block, not on its members. A member which only looks something up about its receiver, such as a name, an icon, a flag, a prompt, or an address, is an extension property, named without `Get` or `To`: `format.FileExtension`, `host.IsChatSupported`. It stays a method when it takes parameters, builds something new such as a list or a model profile, parses, scans a collection, or converts into another type, as `GetModelProfile`, `CompactCount`, and `ToSources` do. Check first that the receiver has no instance member of the same name, which would hide the extension. A helper which belongs to the type rather than to one of its values is a static member of the block and is reached through the type: `RID.Current`, `FileExportFormat.TryFromCodeFenceLanguage`. Never name one `Parse` or `TryParse`: C# finds those of `Enum` first.
|
- **Extension members** - Declare extension methods in an `extension` block of their static class, one block per receiver, never with a `this` parameter: MWAIS0016 refuses the classic form. The `<param>` of the receiver, and of a generic block its `<typeparam>`, belongs on the block, not on its members. A member which only looks something up about its receiver, such as a name, an icon, a flag, a prompt, or an address, is an extension property, named without `Get` or `To`: `format.FileExtension`, `host.IsChatSupported`. It stays a method when it takes parameters, builds something new such as a list or a model profile, parses, scans a collection, or converts into another type, as `GetModelProfile`, `CompactCount`, and `ToSources` do. Check first that the receiver has no instance member of the same name, which would hide the extension. A helper which belongs to the type rather than to one of its values is a static member of the block and is reached through the type: `RID.Current`, `FileExportFormat.TryFromCodeFenceLanguage`. Never name one `Parse` or `TryParse`: C# finds those of `Enum` first.
|
||||||
|
- **Line breaks** - Every text AI Studio builds ends its lines with LF, on every system: use `AppendLineLF` of `SharedTools.Text` instead of `StringBuilder.AppendLine`, and `'\n'` instead of `Environment.NewLine`. Both of them write CR LF on Windows, where no pipeline runs the tests; MWAIS0018 refuses them, and its code fix offers the replacement. Where a format needs CR LF, write it on purpose, as `CsvWriter.ROW_END` does. A raw string literal and a file which comes with the app keep the line endings of the checkout, CR LF on Windows with `core.autocrlf`: a test which compares such a text with what the app built normalizes it first, e.g. with `ReplaceLineEndings("\n")`.
|
||||||
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
||||||
- **Prompt injection reports** - The user hears once per action about what the filter removed, as soon as that action ends. Content which belongs to an action carries its scope from `PromptInjectionGuardService.BeginAction` down to where it is read or filtered; whatever is filtered without a scope is reported right away. Pass the scope as a parameter, never through a field or an `AsyncLocal`: the service serves the whole app, and the message bus delivers to other receivers in the execution context of whoever sends.
|
- **Prompt injection reports** - The user hears once per action about what the filter removed, as soon as that action ends. Content which belongs to an action carries its scope from `PromptInjectionGuardService.BeginAction` down to where it is read or filtered; whatever is filtered without a scope is reported right away. Pass the scope as a parameter, never through a field or an `AsyncLocal`: the service serves the whole app, and the message bus delivers to other receivers in the execution context of whoever sends.
|
||||||
- **Paths in stored data** - A stored path to a file which AI Studio keeps below the data directory, such as a transcript or a tokenizer copy, is relative to that directory, so that a backup or a copy of the data directory works on another computer, under another user name, or on another OS. Mark such a property with `[JsonConverter(typeof(DataDirectoryRelativePathConverter))]`, for a positional record parameter with `[property: ...]`; in memory, the path stays absolute. Better still, derive the path from IDs and store none, as `VisualBriefingStore` does. Paths to files of the user stay absolute. Chats stored before v26.10.1 are migrated by a compatibility shim, see `documentation/compatibility-shims/2026-10-relative-data-paths.md`.
|
- **Paths in stored data** - A stored path to a file which AI Studio keeps below the data directory, such as a transcript or a tokenizer copy, is relative to that directory, so that a backup or a copy of the data directory works on another computer, under another user name, or on another OS. Mark such a property with `[JsonConverter(typeof(DataDirectoryRelativePathConverter))]`, for a positional record parameter with `[property: ...]`; in memory, the path stays absolute. Better still, derive the path from IDs and store none, as `VisualBriefingStore` does. Paths to files of the user stay absolute. Chats stored before v26.10.1 are migrated by a compatibility shim, see `documentation/compatibility-shims/2026-10-relative-data-paths.md`.
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ public sealed class AgentDataSourceSelection (ILogger<AgentDataSourceSelection>
|
|||||||
logger.LogInformation("Preparing the list of allowed data sources for the agent to choose from.");
|
logger.LogInformation("Preparing the list of allowed data sources for the agent to choose from.");
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("The following data sources are available for selection:");
|
sb.AppendLineLF("The following data sources are available for selection:");
|
||||||
foreach (var ds in dataSources.AllowedDataSources)
|
foreach (var ds in dataSources.AllowedDataSources)
|
||||||
{
|
{
|
||||||
var description = await descriptionService.GetDescriptionAsync(ds, token);
|
var description = await descriptionService.GetDescriptionAsync(ds, token);
|
||||||
@@ -198,15 +198,15 @@ public sealed class AgentDataSourceSelection (ILogger<AgentDataSourceSelection>
|
|||||||
switch (ds)
|
switch (ds)
|
||||||
{
|
{
|
||||||
case DataSourceLocalDirectory localDirectory:
|
case DataSourceLocalDirectory localDirectory:
|
||||||
sb.AppendLine($"- Id={ds.Id}, name='{localDirectory.Name}', type=local directory, path='{localDirectory.Path}'{descriptionPart}");
|
sb.AppendLineLF($"- Id={ds.Id}, name='{localDirectory.Name}', type=local directory, path='{localDirectory.Path}'{descriptionPart}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DataSourceLocalFile localFile:
|
case DataSourceLocalFile localFile:
|
||||||
sb.AppendLine($"- Id={ds.Id}, name='{localFile.Name}', type=local file, path='{localFile.FilePath}'{descriptionPart}");
|
sb.AppendLineLF($"- Id={ds.Id}, name='{localFile.Name}', type=local file, path='{localFile.FilePath}'{descriptionPart}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IERIDataSource eriDataSource:
|
case IERIDataSource eriDataSource:
|
||||||
sb.AppendLine($"- Id={ds.Id}, name='{eriDataSource.Name}', type=external data source{descriptionPart}");
|
sb.AppendLineLF($"- Id={ds.Id}, name='{eriDataSource.Name}', type=external data source{descriptionPart}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
|||||||
|
|
||||||
protected override string SystemPrompt(string additionalData) => string.IsNullOrWhiteSpace(additionalData)
|
protected override string SystemPrompt(string additionalData) => string.IsNullOrWhiteSpace(additionalData)
|
||||||
? this.JobDescription
|
? this.JobDescription
|
||||||
: $"{this.JobDescription}{Environment.NewLine}{Environment.NewLine}{additionalData}";
|
: $"{this.JobDescription}\n\n{additionalData}";
|
||||||
|
|
||||||
public override AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE;
|
public override AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE;
|
||||||
|
|
||||||
@@ -343,11 +343,11 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
|||||||
var definition = toolRegistry.GetDefinition(toolId);
|
var definition = toolRegistry.GetDefinition(toolId);
|
||||||
if (definition is null)
|
if (definition is null)
|
||||||
{
|
{
|
||||||
builder.AppendLine($"- {toolId}: unknown to this installation");
|
builder.AppendLineLF($"- {toolId}: unknown to this installation");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine($"- {toolId}: {definition.Function.DescriptionForLLM}");
|
builder.AppendLineLF($"- {toolId}: {definition.Function.DescriptionForLLM}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString().TrimEnd();
|
return builder.ToString().TrimEnd();
|
||||||
@@ -368,11 +368,11 @@ public sealed class AssistantAuditAgent(ILogger<AssistantAuditAgent> logger, ILo
|
|||||||
foreach (var luaFile in luaFiles.OrderBy(file => file.Key, StringComparer.Ordinal))
|
foreach (var luaFile in luaFiles.OrderBy(file => file.Key, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
if (builder.Length > 0)
|
if (builder.Length > 0)
|
||||||
builder.AppendLine().AppendLine();
|
builder.AppendLineLF().AppendLineLF();
|
||||||
|
|
||||||
builder.Append("-- File: ");
|
builder.Append("-- File: ");
|
||||||
builder.AppendLine(luaFile.Key);
|
builder.AppendLineLF(luaFile.Key);
|
||||||
builder.AppendLine(luaFile.Value);
|
builder.AppendLineLF(luaFile.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString().TrimEnd();
|
return builder.ToString().TrimEnd();
|
||||||
|
|||||||
@@ -618,7 +618,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
|
|||||||
{
|
{
|
||||||
var markdown = new StringBuilder();
|
var markdown = new StringBuilder();
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
markdown.AppendLine($"- {item}");
|
markdown.AppendLineLF($"- {item}");
|
||||||
|
|
||||||
return markdown.ToString();
|
return markdown.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1090,7 +1090,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
|
|
||||||
if (outcome.Failures.Count > 0)
|
if (outcome.Failures.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
var message = string.Join('\n',outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
else if (outcome.Status is MediaImportStatus.FAILED)
|
else if (outcome.Status is MediaImportStatus.FAILED)
|
||||||
@@ -1100,7 +1100,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
|||||||
|
|
||||||
if (outcome.Warnings.Count > 0)
|
if (outcome.Warnings.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
var message = string.Join('\n',outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -106,9 +106,9 @@ public partial class AssistantBatchProcessing
|
|||||||
private async Task WriteLogAsync(string resolvedOutputDirectory)
|
private async Task WriteLogAsync(string resolvedOutputDirectory)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details"), T("Tools used")));
|
sb.Append(CsvWriter.ToRow(LOG_SEPARATOR, T("File"), T("Time"), T("Model"), T("Status"), T("Details"), T("Tools used"))).Append(CsvWriter.ROW_END);
|
||||||
foreach (var fileResult in this.fileResults.Where(x => x.Status is not BatchProcessingFileStatus.QUEUED and not BatchProcessingFileStatus.PROCESSING))
|
foreach (var fileResult in this.fileResults.Where(x => x.Status is not BatchProcessingFileStatus.QUEUED and not BatchProcessingFileStatus.PROCESSING))
|
||||||
sb.AppendLine(CsvWriter.ToRow(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message, fileResult.UsedTools));
|
sb.Append(CsvWriter.ToRow(LOG_SEPARATOR, fileResult.RelativePath, fileResult.ProcessedAt.ToString(TIME_FORMAT, CultureInfo.InvariantCulture), fileResult.ModelName, fileResult.Status.ToString(), fileResult.Message, fileResult.UsedTools)).Append(CsvWriter.ROW_END);
|
||||||
|
|
||||||
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, LOG_FILENAME), sb.ToString());
|
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, LOG_FILENAME), sb.ToString());
|
||||||
}
|
}
|
||||||
@@ -120,9 +120,9 @@ public partial class AssistantBatchProcessing
|
|||||||
{
|
{
|
||||||
var separator = this.csvSeparator.Character(this.customCsvSeparator);
|
var separator = this.csvSeparator.Character(this.customCsvSeparator);
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine(CsvWriter.ToRow(separator, T("File"), this.ResultColumnHeader));
|
sb.Append(CsvWriter.ToRow(separator, T("File"), this.ResultColumnHeader)).Append(CsvWriter.ROW_END);
|
||||||
foreach (var fileResult in this.fileResults.Where(x => x.Status is BatchProcessingFileStatus.DONE))
|
foreach (var fileResult in this.fileResults.Where(x => x.Status is BatchProcessingFileStatus.DONE))
|
||||||
sb.AppendLine(CsvWriter.ToRow(separator, fileResult.RelativePath, fileResult.ResultText));
|
sb.Append(CsvWriter.ToRow(separator, fileResult.RelativePath, fileResult.ResultText)).Append(CsvWriter.ROW_END);
|
||||||
|
|
||||||
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, this.ResolveResultsFileName()), sb.ToString());
|
await this.WriteCsvFileAsync(Path.Join(resolvedOutputDirectory, this.ResolveResultsFileName()), sb.ToString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ using System.Text;
|
|||||||
|
|
||||||
using AIStudio.Chat;
|
using AIStudio.Chat;
|
||||||
|
|
||||||
using SharedTools;
|
|
||||||
|
|
||||||
namespace AIStudio.Assistants.BeyondFactsLab;
|
namespace AIStudio.Assistants.BeyondFactsLab;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -111,12 +111,12 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (this.biasOfTheDay.Links.Count > 0)
|
if (this.biasOfTheDay.Links.Count > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
sb.AppendLine("Please share the following sources with the user as a Markdown list:");
|
sb.AppendLineLF("Please share the following sources with the user as a Markdown list:");
|
||||||
foreach (var link in this.biasOfTheDay.Links)
|
foreach (var link in this.biasOfTheDay.Links)
|
||||||
sb.AppendLine($"- {link}");
|
sb.AppendLineLF($"- {link}");
|
||||||
|
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|||||||
@@ -197,12 +197,12 @@ public partial class AssistantCoding : AssistantBaseCore<SettingsDialogCoding>
|
|||||||
var sbCompilerMessages = new StringBuilder();
|
var sbCompilerMessages = new StringBuilder();
|
||||||
if (this.provideCompilerMessages)
|
if (this.provideCompilerMessages)
|
||||||
{
|
{
|
||||||
sbCompilerMessages.AppendLine("I have the following compiler messages:");
|
sbCompilerMessages.AppendLineLF("I have the following compiler messages:");
|
||||||
sbCompilerMessages.AppendLine();
|
sbCompilerMessages.AppendLineLF();
|
||||||
sbCompilerMessages.AppendLine("```");
|
sbCompilerMessages.AppendLineLF("```");
|
||||||
sbCompilerMessages.AppendLine(this.compilerMessages);
|
sbCompilerMessages.AppendLineLF(this.compilerMessages);
|
||||||
sbCompilerMessages.AppendLine("```");
|
sbCompilerMessages.AppendLineLF("```");
|
||||||
sbCompilerMessages.AppendLine();
|
sbCompilerMessages.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.CreateChatThread();
|
this.CreateChatThread();
|
||||||
|
|||||||
+4
-4
@@ -897,7 +897,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
|||||||
|
|
||||||
if (documents.Count > 0)
|
if (documents.Count > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine("""
|
sb.AppendLineLF("""
|
||||||
# DOCUMENTS:
|
# DOCUMENTS:
|
||||||
|
|
||||||
""");
|
""");
|
||||||
@@ -941,7 +941,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileContent = extraction.Content;
|
var fileContent = extraction.Content;
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
## DOCUMENT {numDocuments}:
|
## DOCUMENT {numDocuments}:
|
||||||
File path: {document.FilePath}
|
File path: {document.FilePath}
|
||||||
@@ -961,7 +961,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
|||||||
{
|
{
|
||||||
if (documents.Count == 0)
|
if (documents.Count == 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
There are {numImages} image file(s) attached as documents.
|
There are {numImages} image file(s) attached as documents.
|
||||||
Please consider them as documents as well and use them to
|
Please consider them as documents as well and use them to
|
||||||
@@ -971,7 +971,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPan
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
Additionally, there are {numImages} image file(s) attached.
|
Additionally, there are {numImages} image file(s) attached.
|
||||||
Please consider them as documents as well and use them to
|
Please consider them as documents as well and use them to
|
||||||
|
|||||||
@@ -345,24 +345,24 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
|
|||||||
var componentSummary = this.assistantPlugin.CreateAuditComponentSummary();
|
var componentSummary = this.assistantPlugin.CreateAuditComponentSummary();
|
||||||
if (!string.IsNullOrWhiteSpace(componentSummary))
|
if (!string.IsNullOrWhiteSpace(componentSummary))
|
||||||
{
|
{
|
||||||
builder.AppendLine("Current component overview:");
|
builder.AppendLineLF("Current component overview:");
|
||||||
builder.AppendLine(componentSummary);
|
builder.AppendLineLF(componentSummary);
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var promptPreview = await this.CollectUserPromptAsync();
|
var promptPreview = await this.CollectUserPromptAsync();
|
||||||
if (!string.IsNullOrWhiteSpace(promptPreview))
|
if (!string.IsNullOrWhiteSpace(promptPreview))
|
||||||
{
|
{
|
||||||
builder.AppendLine("Current prompt preview from the assistant form:");
|
builder.AppendLineLF("Current prompt preview from the assistant form:");
|
||||||
builder.AppendLine(promptPreview);
|
builder.AppendLineLF(promptPreview);
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.ResultingContentBlock?.Content is ContentText text && !string.IsNullOrWhiteSpace(text.Text))
|
if (this.ResultingContentBlock?.Content is ContentText text && !string.IsNullOrWhiteSpace(text.Text))
|
||||||
{
|
{
|
||||||
builder.AppendLine("Last assistant response visible in this session:");
|
builder.AppendLineLF("Last assistant response visible in this session:");
|
||||||
builder.AppendLine(text.Text);
|
builder.AppendLineLF(text.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString().Trim();
|
return builder.ToString().Trim();
|
||||||
@@ -740,6 +740,6 @@ public partial class AssistantDynamic : AssistantBaseCore<NoSettingsPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return prompt.Append(Environment.NewLine).ToString();
|
return prompt.AppendLineLF().ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,9 +256,9 @@ public partial class AssistantEMail : AssistantBaseCore<SettingsDialogWritingEMa
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("I want to amplify the following points:");
|
sb.AppendLineLF("I want to amplify the following points:");
|
||||||
foreach (var focus in this.selectedFoci)
|
foreach (var focus in this.selectedFoci)
|
||||||
sb.AppendLine($"- {focus}");
|
sb.AppendLineLF($"- {focus}");
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,50 +18,50 @@ internal static class SwotAnalysisMarkdownFormatter
|
|||||||
var markdown = new StringBuilder();
|
var markdown = new StringBuilder();
|
||||||
|
|
||||||
AppendHeading(markdown, result.MatrixHeading);
|
AppendHeading(markdown, result.MatrixHeading);
|
||||||
markdown.AppendLine($"| | {EscapeTableCell(result.PositiveLabel)} | {EscapeTableCell(result.NegativeLabel)} |");
|
markdown.AppendLineLF($"| | {EscapeTableCell(result.PositiveLabel)} | {EscapeTableCell(result.NegativeLabel)} |");
|
||||||
markdown.AppendLine("|---|---|---|");
|
markdown.AppendLineLF("|---|---|---|");
|
||||||
markdown.AppendLine($"| **{EscapeTableCell(result.InternalLabel)}** | {FormatCategoryCell(result.Strengths)} | {FormatCategoryCell(result.Weaknesses)} |");
|
markdown.AppendLineLF($"| **{EscapeTableCell(result.InternalLabel)}** | {FormatCategoryCell(result.Strengths)} | {FormatCategoryCell(result.Weaknesses)} |");
|
||||||
markdown.AppendLine($"| **{EscapeTableCell(result.ExternalLabel)}** | {FormatCategoryCell(result.Opportunities)} | {FormatCategoryCell(result.Threats)} |");
|
markdown.AppendLineLF($"| **{EscapeTableCell(result.ExternalLabel)}** | {FormatCategoryCell(result.Opportunities)} | {FormatCategoryCell(result.Threats)} |");
|
||||||
markdown.AppendLine();
|
markdown.AppendLineLF();
|
||||||
|
|
||||||
AppendHeading(markdown, result.OverallAssessment.Label);
|
AppendHeading(markdown, result.OverallAssessment.Label);
|
||||||
foreach (var paragraph in result.OverallAssessment.Paragraphs)
|
foreach (var paragraph in result.OverallAssessment.Paragraphs)
|
||||||
{
|
{
|
||||||
markdown.AppendLine(Markdown.EscapeInlineText(paragraph));
|
markdown.AppendLineLF(Markdown.EscapeInlineText(paragraph));
|
||||||
markdown.AppendLine();
|
markdown.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendHeading(markdown, result.PrioritizedActions.Label);
|
AppendHeading(markdown, result.PrioritizedActions.Label);
|
||||||
if (result.PrioritizedActions.Items.Count == 0)
|
if (result.PrioritizedActions.Items.Count == 0)
|
||||||
markdown.AppendLine(Markdown.EscapeInlineText(result.PrioritizedActions.EmptyMessage));
|
markdown.AppendLineLF(Markdown.EscapeInlineText(result.PrioritizedActions.EmptyMessage));
|
||||||
else
|
else
|
||||||
for (var index = 0; index < result.PrioritizedActions.Items.Count; index++)
|
for (var index = 0; index < result.PrioritizedActions.Items.Count; index++)
|
||||||
{
|
{
|
||||||
var action = result.PrioritizedActions.Items[index];
|
var action = result.PrioritizedActions.Items[index];
|
||||||
var factors = string.Join("; ", action.AddressedFactors.Select(Markdown.EscapeInlineText));
|
var factors = string.Join("; ", action.AddressedFactors.Select(Markdown.EscapeInlineText));
|
||||||
markdown.AppendLine($"{index + 1}. **{Markdown.EscapeInlineText(action.Action)}** — {Markdown.EscapeInlineText(action.Rationale)} ");
|
markdown.AppendLineLF($"{index + 1}. **{Markdown.EscapeInlineText(action.Action)}** — {Markdown.EscapeInlineText(action.Rationale)} ");
|
||||||
markdown.AppendLine($" *{factors}*");
|
markdown.AppendLineLF($" *{factors}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
markdown.AppendLine();
|
markdown.AppendLineLF();
|
||||||
foreach (var category in result.Categories)
|
foreach (var category in result.Categories)
|
||||||
{
|
{
|
||||||
AppendHeading(markdown, category.Label);
|
AppendHeading(markdown, category.Label);
|
||||||
if (category.Findings.Count == 0)
|
if (category.Findings.Count == 0)
|
||||||
markdown.AppendLine(Markdown.EscapeInlineText(category.EmptyMessage));
|
markdown.AppendLineLF(Markdown.EscapeInlineText(category.EmptyMessage));
|
||||||
else
|
else
|
||||||
foreach (var finding in category.Findings)
|
foreach (var finding in category.Findings)
|
||||||
markdown.AppendLine($"- **{Markdown.EscapeInlineText(finding.Summary)}** — {Markdown.EscapeInlineText(finding.Explanation)}");
|
markdown.AppendLineLF($"- **{Markdown.EscapeInlineText(finding.Summary)}** — {Markdown.EscapeInlineText(finding.Explanation)}");
|
||||||
|
|
||||||
markdown.AppendLine();
|
markdown.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendHeading(markdown, result.ModerationNotes.Label);
|
AppendHeading(markdown, result.ModerationNotes.Label);
|
||||||
if (result.ModerationNotes.Items.Count == 0)
|
if (result.ModerationNotes.Items.Count == 0)
|
||||||
markdown.AppendLine(Markdown.EscapeInlineText(result.ModerationNotes.EmptyMessage));
|
markdown.AppendLineLF(Markdown.EscapeInlineText(result.ModerationNotes.EmptyMessage));
|
||||||
else
|
else
|
||||||
foreach (var note in result.ModerationNotes.Items)
|
foreach (var note in result.ModerationNotes.Items)
|
||||||
markdown.AppendLine($"- **{Markdown.EscapeInlineText(note.Change)}** — {Markdown.EscapeInlineText(note.Reason)}");
|
markdown.AppendLineLF($"- **{Markdown.EscapeInlineText(note.Change)}** — {Markdown.EscapeInlineText(note.Reason)}");
|
||||||
|
|
||||||
return markdown.ToString().TrimEnd();
|
return markdown.ToString().TrimEnd();
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ internal static class SwotAnalysisMarkdownFormatter
|
|||||||
private static void AppendHeading(StringBuilder markdown, string heading)
|
private static void AppendHeading(StringBuilder markdown, string heading)
|
||||||
{
|
{
|
||||||
markdown.Append("## ");
|
markdown.Append("## ");
|
||||||
markdown.AppendLine(Markdown.EscapeInlineText(heading));
|
markdown.AppendLineLF(Markdown.EscapeInlineText(heading));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatCategoryCell(SwotCategory category)
|
private static string FormatCategoryCell(SwotCategory category)
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ public partial class SlideAssistant
|
|||||||
if (library.Pictures.Count == 0)
|
if (library.Pictures.Count == 0)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var list = string.Join(Environment.NewLine, library.Pictures.Select((picture, index) => $"- Picture {firstNumber + index}: {picture.Name}{(picture.IsIcon ? " (icon)" : string.Empty)}"));
|
var list = string.Join('\n',library.Pictures.Select((picture, index) => $"- Picture {firstNumber + index}: {picture.Name}{(picture.IsIcon ? " (icon)" : string.Empty)}"));
|
||||||
var sanitizedList = await guardService.SanitizeAsync(list, PromptInjectionSource.FileContent(folderPath), promptInjectionScope);
|
var sanitizedList = await guardService.SanitizeAsync(list, PromptInjectionSource.FileContent(folderPath), promptInjectionScope);
|
||||||
return $"""
|
return $"""
|
||||||
|
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ public partial class SlideAssistant
|
|||||||
if (this.SourceImagesOfRequest().Count > 0)
|
if (this.SourceImagesOfRequest().Count > 0)
|
||||||
rules.Add(" - Images attached as source material are for reading only. Never place them on a slide.");
|
rules.Add(" - Images attached as source material are for reading only. Never place them on a slide.");
|
||||||
|
|
||||||
return string.Join(Environment.NewLine, rules);
|
return string.Join('\n',rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -376,7 +376,7 @@ public partial class SlideAssistant
|
|||||||
if (pictures.Count == 0)
|
if (pictures.Count == 0)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var list = string.Join(Environment.NewLine, pictures.Select((picture, index) => $"- Picture {index + 1}: {picture.FileName}"));
|
var list = string.Join('\n',pictures.Select((picture, index) => $"- Picture {index + 1}: {picture.FileName}"));
|
||||||
var seeing = modelSeesPictures
|
var seeing = modelSeesPictures
|
||||||
? "The images attached to this request start with these pictures, in this order."
|
? "The images attached to this request start with these pictures, in this order."
|
||||||
: "You cannot see these pictures. Place them by what their names say.";
|
: "You cannot see these pictures. Place them by what their names say.";
|
||||||
@@ -516,7 +516,7 @@ public partial class SlideAssistant
|
|||||||
|
|
||||||
private static string RepairPromptOf(IReadOnlyList<string> issues)
|
private static string RepairPromptOf(IReadOnlyList<string> issues)
|
||||||
{
|
{
|
||||||
var listedIssues = string.Join(Environment.NewLine, issues.Take(MAX_REPORTED_ISSUES).Select(issue => $"- {issue}"));
|
var listedIssues = string.Join('\n',issues.Take(MAX_REPORTED_ISSUES).Select(issue => $"- {issue}"));
|
||||||
return $"""
|
return $"""
|
||||||
Your JSON does not fit the layouts yet:
|
Your JSON does not fit the layouts yet:
|
||||||
{listedIssues}
|
{listedIssues}
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
|
|||||||
if (prompts.Count == 0)
|
if (prompts.Count == 0)
|
||||||
return " - Do not tailor the text to a specific audience.";
|
return " - Do not tailor the text to a specific audience.";
|
||||||
|
|
||||||
return string.Join(Environment.NewLine, prompts.Select(prompt => $" - {prompt}"));
|
return string.Join('\n',prompts.Select(prompt => $" - {prompt}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDocumentTaskDescription()
|
private string GetDocumentTaskDescription()
|
||||||
@@ -577,7 +577,7 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
|
|||||||
|
|
||||||
if (documents.Count > 0)
|
if (documents.Count > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine("""
|
sb.AppendLineLF("""
|
||||||
# DOCUMENTS:
|
# DOCUMENTS:
|
||||||
|
|
||||||
""");
|
""");
|
||||||
@@ -614,7 +614,7 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileContent = extraction.Content;
|
var fileContent = extraction.Content;
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
## DOCUMENT {numDocuments}:
|
## DOCUMENT {numDocuments}:
|
||||||
File path: {document.FilePath}
|
File path: {document.FilePath}
|
||||||
@@ -634,7 +634,7 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
|
|||||||
{
|
{
|
||||||
if (documents.Count == 0)
|
if (documents.Count == 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
There are {numImages} image file(s) attached as source material.
|
There are {numImages} image file(s) attached as source material.
|
||||||
Please consider them as documents as well and use them to
|
Please consider them as documents as well and use them to
|
||||||
@@ -644,7 +644,7 @@ public partial class SlideAssistant : AssistantBaseCore<SettingsDialogSlideBuild
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
|
|
||||||
Additionally, there are {numImages} image file(s) attached as source material.
|
Additionally, there are {numImages} image file(s) attached as source material.
|
||||||
Please consider them as documents as well and use them to
|
Please consider them as documents as well and use them to
|
||||||
|
|||||||
+2
-2
@@ -188,13 +188,13 @@ public partial class VisualBriefingAssistant
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (outcome.Failures.Count > 0)
|
if (outcome.Failures.Count > 0)
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, string.Join(Environment.NewLine, outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"))));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, string.Join('\n',outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"))));
|
||||||
|
|
||||||
else if (outcome.Status is MediaImportStatus.FAILED)
|
else if (outcome.Status is MediaImportStatus.FAILED)
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, T("The media file could not be transcribed.")));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, T("The media file could not be transcribed.")));
|
||||||
|
|
||||||
if (outcome.Warnings.Count > 0)
|
if (outcome.Warnings.Count > 0)
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, string.Join(Environment.NewLine, outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"))));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, string.Join('\n',outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"))));
|
||||||
|
|
||||||
if (outcome.Status is MediaImportStatus.CANCELLED)
|
if (outcome.Status is MediaImportStatus.CANCELLED)
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, T("The media transcription was canceled.")));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, T("The media transcription was canceled.")));
|
||||||
|
|||||||
+2
-2
@@ -78,7 +78,7 @@ internal static partial class VisualBriefingStructuredResponseProcessor
|
|||||||
var root = GetContract(typeof(T));
|
var root = GetContract(typeof(T));
|
||||||
var shapes = EnumerateObjectShapes(root);
|
var shapes = EnumerateObjectShapes(root);
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine("Strict JSON grammar generated from the active response contract:");
|
builder.AppendLineLF("Strict JSON grammar generated from the active response contract:");
|
||||||
foreach (var shape in shapes)
|
foreach (var shape in shapes)
|
||||||
{
|
{
|
||||||
builder.Append(shape.Name);
|
builder.Append(shape.Name);
|
||||||
@@ -98,7 +98,7 @@ internal static partial class VisualBriefingStructuredResponseProcessor
|
|||||||
if (property.AllowsNull)
|
if (property.AllowsNull)
|
||||||
builder.Append(" | null");
|
builder.Append(" | null");
|
||||||
}
|
}
|
||||||
builder.AppendLine("}");
|
builder.AppendLineLF("}");
|
||||||
}
|
}
|
||||||
builder.Append(
|
builder.Append(
|
||||||
"Every object may contain only the properties shown above. Required properties must be present even when their value is null.");
|
"Every object may contain only the properties shown above. Required properties must be present even when their value is null.");
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public static class ContentExtensions
|
|||||||
if (sources.Length is 0 || !file.Format.TryToComment(sources, out var comment))
|
if (sources.Length is 0 || !file.Format.TryToComment(sources, out var comment))
|
||||||
return file.Content;
|
return file.Content;
|
||||||
|
|
||||||
return $"{file.Content}{Environment.NewLine}{Environment.NewLine}{comment}";
|
return $"{file.Content}\n\n{comment}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -775,7 +775,7 @@ public sealed class ContentText : IContent
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var attachmentWebAddresses = new HashSet<string>(StringComparer.Ordinal);
|
var attachmentWebAddresses = new HashSet<string>(StringComparer.Ordinal);
|
||||||
sb.AppendLine(this.Text);
|
sb.AppendLineLF(this.Text);
|
||||||
|
|
||||||
if(this.FileAttachments.Count > 0)
|
if(this.FileAttachments.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -856,13 +856,13 @@ public sealed class ContentText : IContent
|
|||||||
content = extraction.Content;
|
content = extraction.Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
documentBlocks.AppendLine();
|
documentBlocks.AppendLineLF();
|
||||||
documentBlocks.AppendLine("---------------------------------------");
|
documentBlocks.AppendLineLF("---------------------------------------");
|
||||||
documentBlocks.AppendLine($"File path: {document.FilePath}");
|
documentBlocks.AppendLineLF($"File path: {document.FilePath}");
|
||||||
documentBlocks.AppendLine("File content:");
|
documentBlocks.AppendLineLF("File content:");
|
||||||
documentBlocks.AppendLine("````");
|
documentBlocks.AppendLineLF("````");
|
||||||
documentBlocks.AppendLine(content);
|
documentBlocks.AppendLineLF(content);
|
||||||
documentBlocks.AppendLine("````");
|
documentBlocks.AppendLineLF("````");
|
||||||
|
|
||||||
foreach (var address in WebAddresses.Find(content))
|
foreach (var address in WebAddresses.Find(content))
|
||||||
if (WebAddresses.TryCreateRequestKey(address, out var requestKey))
|
if (WebAddresses.TryCreateRequestKey(address, out var requestKey))
|
||||||
@@ -871,17 +871,17 @@ public sealed class ContentText : IContent
|
|||||||
|
|
||||||
if (documentBlocks.Length > 0)
|
if (documentBlocks.Length > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
sb.AppendLine("The following files are attached to this message:");
|
sb.AppendLineLF("The following files are attached to this message:");
|
||||||
sb.Append(documentBlocks);
|
sb.Append(documentBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
var numImages = normalizedAttachments.Count(x => x is { IsImage: true, Exists: true });
|
var numImages = normalizedAttachments.Count(x => x is { IsImage: true, Exists: true });
|
||||||
if (numImages > 0)
|
if (numImages > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
sb.AppendLine($"Additionally, there are {numImages} image file(s) attached to this message. ");
|
sb.AppendLineLF($"Additionally, there are {numImages} image file(s) attached to this message. ");
|
||||||
sb.AppendLine("Please consider them as part of the message content and use them to answer accordingly.");
|
sb.AppendLineLF("Please consider them as part of the message content and use them to answer accordingly.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ internal static class MarkdownRenderPlanner
|
|||||||
return cleaned;
|
return cleaned;
|
||||||
|
|
||||||
if (LooksLikeRawHtml(cleaned))
|
if (LooksLikeRawHtml(cleaned))
|
||||||
return $"{HTML_CODE_FENCE_PREFIX}{Environment.NewLine}{cleaned}{Environment.NewLine}{CODE_FENCE_MARKER_BACKTICK}";
|
return $"{HTML_CODE_FENCE_PREFIX}\n{cleaned}\n{CODE_FENCE_MARKER_BACKTICK}";
|
||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,5 +38,5 @@ public partial class MathJaxBlock
|
|||||||
? "formula-block"
|
? "formula-block"
|
||||||
: $"formula-block {this.Class}";
|
: $"formula-block {this.Class}";
|
||||||
|
|
||||||
private string MathText => $"$${Environment.NewLine}{this.Value}{Environment.NewLine}$$";
|
private string MathText => $"$$\n{this.Value}\n$$";
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ public partial class AttachDocuments : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Failures.Count > 0)
|
if (outcome.Failures.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
var message = string.Join('\n',outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
else if (outcome.Status is MediaImportStatus.FAILED)
|
else if (outcome.Status is MediaImportStatus.FAILED)
|
||||||
@@ -181,7 +181,7 @@ public partial class AttachDocuments : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Warnings.Count > 0)
|
if (outcome.Warnings.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
var message = string.Join('\n',outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ public partial class ChatComponent : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Failures.Count > 0)
|
if (outcome.Failures.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
var message = string.Join('\n',outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
else if (outcome.Status is MediaImportStatus.FAILED)
|
else if (outcome.Status is MediaImportStatus.FAILED)
|
||||||
@@ -542,7 +542,7 @@ public partial class ChatComponent : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Warnings.Count > 0)
|
if (outcome.Warnings.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
var message = string.Join('\n',outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public partial class ReadFileContent : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Failures.Count > 0)
|
if (outcome.Failures.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
var message = string.Join('\n',outcome.Failures.Select(failure => $"{failure.FileName}: {failure.UserMessage}"));
|
||||||
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendError(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
else if (outcome.Status is MediaImportStatus.FAILED)
|
else if (outcome.Status is MediaImportStatus.FAILED)
|
||||||
@@ -153,7 +153,7 @@ public partial class ReadFileContent : MSGComponentBase
|
|||||||
|
|
||||||
if (outcome.Warnings.Count > 0)
|
if (outcome.Warnings.Count > 0)
|
||||||
{
|
{
|
||||||
var message = string.Join(Environment.NewLine, outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
var message = string.Join('\n',outcome.Warnings.Select(warning => $"{warning.FileName}: {warning.UserMessage}"));
|
||||||
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
await this.MessageBus.SendWarning(new(AppIcons.VOICE_TRANSCRIPTION, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public partial class SettingsPanelApp : SettingsPanelBase
|
|||||||
|
|
||||||
private string GetExternalHttpCustomRootCertificateAllowedHostsText()
|
private string GetExternalHttpCustomRootCertificateAllowedHostsText()
|
||||||
{
|
{
|
||||||
return string.Join(Environment.NewLine, this.SettingsManager.ConfigurationData.App.ExternalHttpCustomRootCertificateAllowedHosts.Order(StringComparer.OrdinalIgnoreCase));
|
return string.Join('\n',this.SettingsManager.ConfigurationData.App.ExternalHttpCustomRootCertificateAllowedHosts.Order(StringComparer.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AreExternalHttpCustomRootCertificateDetailsDisabled()
|
private bool AreExternalHttpCustomRootCertificateDetailsDisabled()
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
|||||||
//
|
//
|
||||||
var dialogParameters = string.IsNullOrEmpty(affectedDataSources)
|
var dialogParameters = string.IsNullOrEmpty(affectedDataSources)
|
||||||
? new DialogParameters<ConfirmDialog> { { x => x.Message, question } }
|
? new DialogParameters<ConfirmDialog> { { x => x.Message, question } }
|
||||||
: new DialogParameters<ConfirmDialog> { { x => x.MarkdownBody, $"{affectedDataSources}{Environment.NewLine}{question}" } };
|
: new DialogParameters<ConfirmDialog> { { x => x.MarkdownBody, $"{affectedDataSources}\n{question}" } };
|
||||||
|
|
||||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
|
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||||
var dialogResult = await dialogReference.Result;
|
var dialogResult = await dialogReference.Result;
|
||||||
@@ -227,7 +227,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultText = string.Join(Environment.NewLine, vector.Select(value => value.ToString("G9", CultureInfo.InvariantCulture)));
|
var resultText = string.Join('\n',vector.Select(value => value.ToString("G9", CultureInfo.InvariantCulture)));
|
||||||
var resultParameters = new DialogParameters<EmbeddingResultDialog>
|
var resultParameters = new DialogParameters<EmbeddingResultDialog>
|
||||||
{
|
{
|
||||||
{ x => x.ResultText, resultText },
|
{ x => x.ResultText, resultText },
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ public partial class DataSourceERI_V1InfoDialog : MSGComponentBase, ISecretId
|
|||||||
foreach (var (paramName, description) in parameters)
|
foreach (var (paramName, description) in parameters)
|
||||||
{
|
{
|
||||||
sb.Append(T("Parameter: "));
|
sb.Append(T("Parameter: "));
|
||||||
sb.AppendLine(paramName);
|
sb.AppendLineLF(paramName);
|
||||||
sb.AppendLine(description);
|
sb.AppendLineLF(description);
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase
|
|||||||
private void UpdateFileList(string file)
|
private void UpdateFileList(string file)
|
||||||
{
|
{
|
||||||
this.directoryFiles.Append("- ");
|
this.directoryFiles.Append("- ");
|
||||||
this.directoryFiles.AppendLine(file);
|
this.directoryFiles.AppendLineLF(file);
|
||||||
this.directoryFilesText = this.directoryFiles.ToString();
|
this.directoryFilesText = this.directoryFiles.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ global using AIStudio.Tools;
|
|||||||
global using Microsoft.JSInterop;
|
global using Microsoft.JSInterop;
|
||||||
|
|
||||||
global using MudBlazor;
|
global using MudBlazor;
|
||||||
global using MudExtensions;
|
global using MudExtensions;
|
||||||
|
|
||||||
|
global using SharedTools.Text;
|
||||||
@@ -47,7 +47,7 @@ public sealed class AnthropicToolCallingAdapter(Model chatModel, IList<IMessageB
|
|||||||
Messages = [..baseMessages, ..this.internalMessages],
|
Messages = [..baseMessages, ..this.internalMessages],
|
||||||
System = finalResponseInstruction is null
|
System = finalResponseInstruction is null
|
||||||
? systemPrompt
|
? systemPrompt
|
||||||
: $"{systemPrompt}{Environment.NewLine}{Environment.NewLine}{finalResponseInstruction}",
|
: $"{systemPrompt}\n\n{finalResponseInstruction}",
|
||||||
|
|
||||||
MaxTokens = maxTokens,
|
MaxTokens = maxTokens,
|
||||||
Stream = true,
|
Stream = true,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public sealed class ChatCompletionToolCallingAdapter<TRequest>(
|
|||||||
var requestSystemPrompt = finalResponseInstruction is null
|
var requestSystemPrompt = finalResponseInstruction is null
|
||||||
? systemPrompt : systemPrompt with
|
? systemPrompt : systemPrompt with
|
||||||
{
|
{
|
||||||
Content = $"{systemPrompt.Content}{Environment.NewLine}{Environment.NewLine}{finalResponseInstruction}",
|
Content = $"{systemPrompt.Content}\n\n{finalResponseInstruction}",
|
||||||
};
|
};
|
||||||
|
|
||||||
ChatCompletionAPIRequest requestDtoBase = await requestFactory(requestSystemPrompt, apiParameters, includeTools ? providerTools : null);
|
ChatCompletionAPIRequest requestDtoBase = await requestFactory(requestSystemPrompt, apiParameters, includeTools ? providerTools : null);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
|||||||
{
|
{
|
||||||
requestInput[0] = systemPrompt with
|
requestInput[0] = systemPrompt with
|
||||||
{
|
{
|
||||||
Content = $"{systemPrompt.Content}{Environment.NewLine}{Environment.NewLine}{finalResponseInstruction}",
|
Content = $"{systemPrompt.Content}\n\n{finalResponseInstruction}",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ public record ChatTemplate(
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine("{");
|
builder.AppendLineLF("{");
|
||||||
for (var i = 0; i < this.ExampleConversation.Count; i++)
|
for (var i = 0; i < this.ExampleConversation.Count; i++)
|
||||||
{
|
{
|
||||||
var block = this.ExampleConversation[i];
|
var block = this.ExampleConversation[i];
|
||||||
@@ -680,10 +680,10 @@ public record ChatTemplate(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine(" {");
|
builder.AppendLineLF(" {");
|
||||||
builder.AppendLine($" [\"Role\"] = \"{block.Role}\",");
|
builder.AppendLineLF($" [\"Role\"] = \"{block.Role}\",");
|
||||||
builder.AppendLine($" [\"Content\"] = {LuaTools.ToLuaStringLiteral(textContent.Text)},");
|
builder.AppendLineLF($" [\"Content\"] = {LuaTools.ToLuaStringLiteral(textContent.Text)},");
|
||||||
builder.AppendLine(" },");
|
builder.AppendLineLF(" },");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(" }");
|
builder.Append(" }");
|
||||||
@@ -693,7 +693,7 @@ public record ChatTemplate(
|
|||||||
|
|
||||||
private static string BuildOptionalStringLua(string fieldName, string? value) => value is null
|
private static string BuildOptionalStringLua(string fieldName, string? value) => value is null
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: $"{Environment.NewLine} [\"{fieldName}\"] = {LuaTools.ToLuaStringLiteral(value)},";
|
: $"\n [\"{fieldName}\"] = {LuaTools.ToLuaStringLiteral(value)},";
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// An empty set is written out as an empty table rather than being left out: the two say
|
/// An empty set is written out as an empty table rather than being left out: the two say
|
||||||
@@ -706,21 +706,21 @@ public record ChatTemplate(
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
if (this.ToolIds.Count == 0)
|
if (this.ToolIds.Count == 0)
|
||||||
{
|
{
|
||||||
builder.Append(""" ["ToolIds"] = {},""");
|
builder.Append(""" ["ToolIds"] = {},""");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine(""" ["ToolIds"] = {""");
|
builder.AppendLineLF(""" ["ToolIds"] = {""");
|
||||||
|
|
||||||
//
|
//
|
||||||
// A set has no order of its own, so exporting the same template twice would otherwise
|
// A set has no order of its own, so exporting the same template twice would otherwise
|
||||||
// produce two different files. Sorting keeps the plugin diffs readable:
|
// produce two different files. Sorting keeps the plugin diffs readable:
|
||||||
//
|
//
|
||||||
foreach (var toolId in this.ToolIds.Order(StringComparer.Ordinal))
|
foreach (var toolId in this.ToolIds.Order(StringComparer.Ordinal))
|
||||||
builder.AppendLine($" {LuaTools.ToLuaStringLiteral(toolId)},");
|
builder.AppendLineLF($" {LuaTools.ToLuaStringLiteral(toolId)},");
|
||||||
|
|
||||||
builder.Append(" },");
|
builder.Append(" },");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
@@ -732,22 +732,22 @@ public record ChatTemplate(
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
builder.AppendLine(""" ["DataSourceOptions"] = {""");
|
builder.AppendLineLF(""" ["DataSourceOptions"] = {""");
|
||||||
builder.AppendLine($""" ["DisableDataSources"] = {options.DisableDataSources.ToString().ToLowerInvariant()},""");
|
builder.AppendLineLF($""" ["DisableDataSources"] = {options.DisableDataSources.ToString().ToLowerInvariant()},""");
|
||||||
builder.AppendLine($""" ["AutomaticDataSourceSelection"] = {options.AutomaticDataSourceSelection.ToString().ToLowerInvariant()},""");
|
builder.AppendLineLF($""" ["AutomaticDataSourceSelection"] = {options.AutomaticDataSourceSelection.ToString().ToLowerInvariant()},""");
|
||||||
builder.AppendLine($""" ["AutomaticValidation"] = {options.AutomaticValidation.ToString().ToLowerInvariant()},""");
|
builder.AppendLineLF($""" ["AutomaticValidation"] = {options.AutomaticValidation.ToString().ToLowerInvariant()},""");
|
||||||
builder.AppendLine($""" ["RetrievalMode"] = "{options.RetrievalMode}",""");
|
builder.AppendLineLF($""" ["RetrievalMode"] = "{options.RetrievalMode}",""");
|
||||||
|
|
||||||
if (options.PreselectedDataSourceIds.Count == 0)
|
if (options.PreselectedDataSourceIds.Count == 0)
|
||||||
builder.AppendLine(""" ["PreselectedDataSourceIds"] = {},""");
|
builder.AppendLineLF(""" ["PreselectedDataSourceIds"] = {},""");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.AppendLine(""" ["PreselectedDataSourceIds"] = {""");
|
builder.AppendLineLF(""" ["PreselectedDataSourceIds"] = {""");
|
||||||
foreach (var dataSourceId in options.PreselectedDataSourceIds)
|
foreach (var dataSourceId in options.PreselectedDataSourceIds)
|
||||||
builder.AppendLine($" {LuaTools.ToLuaStringLiteral(dataSourceId)},");
|
builder.AppendLineLF($" {LuaTools.ToLuaStringLiteral(dataSourceId)},");
|
||||||
|
|
||||||
builder.AppendLine(" },");
|
builder.AppendLineLF(" },");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(" },");
|
builder.Append(" },");
|
||||||
@@ -801,9 +801,9 @@ public record ChatTemplate(
|
|||||||
return "{}";
|
return "{}";
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine("{");
|
builder.AppendLineLF("{");
|
||||||
foreach (var path in paths)
|
foreach (var path in paths)
|
||||||
builder.AppendLine($" \"{LuaTools.EscapeLuaString(path)}\",");
|
builder.AppendLineLF($" \"{LuaTools.EscapeLuaString(path)}\",");
|
||||||
|
|
||||||
builder.Append(" }");
|
builder.Append(" }");
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
|
|||||||
@@ -93,6 +93,6 @@ public static class ExportedTokenizerPath
|
|||||||
field,
|
field,
|
||||||
];
|
];
|
||||||
|
|
||||||
return string.Join($"{Environment.NewLine}{indentation}", lines);
|
return string.Join($"\n{indentation}", lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,14 +346,14 @@ public sealed record ProviderCapabilityOverrides
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine($@"{indentation}[""CapabilityOverrides""] = {{");
|
builder.AppendLineLF($@"{indentation}[""CapabilityOverrides""] = {{");
|
||||||
foreach (var capability in SUPPORTED_CAPABILITIES)
|
foreach (var capability in SUPPORTED_CAPABILITIES)
|
||||||
{
|
{
|
||||||
var overrideValue = this.GetOverride(capability);
|
var overrideValue = this.GetOverride(capability);
|
||||||
if (overrideValue is null)
|
if (overrideValue is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
builder.AppendLine($@"{indentation} [""{capability}""] = {overrideValue.Value.ToString().ToLowerInvariant()},");
|
builder.AppendLineLF($@"{indentation} [""{capability}""] = {overrideValue.Value.ToString().ToLowerInvariant()},");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var key in NUMERIC_KEYS)
|
foreach (var key in NUMERIC_KEYS)
|
||||||
@@ -361,7 +361,7 @@ public sealed record ProviderCapabilityOverrides
|
|||||||
if (this.GetNumber(key) is not { } number)
|
if (this.GetNumber(key) is not { } number)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
builder.AppendLine($@"{indentation} [""{key}""] = {number.ToString(CultureInfo.InvariantCulture)},");
|
builder.AppendLineLF($@"{indentation} [""{key}""] = {number.ToString(CultureInfo.InvariantCulture)},");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append($@"{indentation}}},");
|
builder.Append($@"{indentation}}},");
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ public static class ContentStreamSseHandler
|
|||||||
var spreadSheetResult = new StringBuilder();
|
var spreadSheetResult = new StringBuilder();
|
||||||
if (rowNumber == 0)
|
if (rowNumber == 0)
|
||||||
{
|
{
|
||||||
spreadSheetResult.AppendLine();
|
spreadSheetResult.AppendLineLF();
|
||||||
spreadSheetResult.AppendLine($"# {sheetName}");
|
spreadSheetResult.AppendLineLF($"# {sheetName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
spreadSheetResult.Append(sseEvent.Content);
|
spreadSheetResult.Append(sseEvent.Content);
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ namespace AIStudio.Tools;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CsvWriter
|
public static class CsvWriter
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The end of every row: CR LF, as RFC 4180 asks for, on every system.
|
||||||
|
/// </summary>
|
||||||
|
public const string ROW_END = "\r\n";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The separator a spreadsheet expects from a CSV file written for the given language.
|
/// The separator a spreadsheet expects from a CSV file written for the given language.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -122,18 +122,18 @@ public static class DataSourceReindexWarning
|
|||||||
private static async Task<bool> ConfirmNewLocationAsync(IDialogService dialogService, bool usesCloudEmbedding)
|
private static async Task<bool> ConfirmNewLocationAsync(IDialogService dialogService, bool usesCloudEmbedding)
|
||||||
{
|
{
|
||||||
var body = new StringBuilder();
|
var body = new StringBuilder();
|
||||||
body.AppendLine(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
|
body.AppendLineLF(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
|
body.AppendLineLF(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
|
||||||
|
|
||||||
if (usesCloudEmbedding)
|
if (usesCloudEmbedding)
|
||||||
{
|
{
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
|
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
|
||||||
}
|
}
|
||||||
|
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Do you want to apply this change anyway?"));
|
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
|
||||||
|
|
||||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||||
{
|
{
|
||||||
@@ -173,11 +173,11 @@ public static class DataSourceReindexWarning
|
|||||||
var body = new StringBuilder();
|
var body = new StringBuilder();
|
||||||
|
|
||||||
// Counted rather than put into a plural form: the I18N has no mechanism for one.
|
// Counted rather than put into a plural form: the I18N has no mechanism for one.
|
||||||
body.AppendLine(string.Format(TB("These data sources are set up with this embedding provider ({0}):"), affected.Count.CompactCount()));
|
body.AppendLineLF(string.Format(TB("These data sources are set up with this embedding provider ({0}):"), affected.Count.CompactCount()));
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(FormatDataSourceNames(affected));
|
body.AppendLineLF(FormatDataSourceNames(affected));
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
|
body.AppendLineLF(TB("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
|
||||||
|
|
||||||
return body.ToString();
|
return body.ToString();
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ public static class DataSourceReindexWarning
|
|||||||
if (names.Count > MAX_NAMED_DATA_SOURCES)
|
if (names.Count > MAX_NAMED_DATA_SOURCES)
|
||||||
lines.Add($"- {string.Format(TB("and {0} more."), (names.Count - MAX_NAMED_DATA_SOURCES).CompactCount())}");
|
lines.Add($"- {string.Format(TB("and {0} more."), (names.Count - MAX_NAMED_DATA_SOURCES).CompactCount())}");
|
||||||
|
|
||||||
return string.Join(Environment.NewLine, lines);
|
return string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<bool> ConfirmAsync(IDialogService dialogService, IReadOnlyList<IDataSourceBase> affected, bool usesCloudEmbedding)
|
private static async Task<bool> ConfirmAsync(IDialogService dialogService, IReadOnlyList<IDataSourceBase> affected, bool usesCloudEmbedding)
|
||||||
@@ -222,20 +222,20 @@ public static class DataSourceReindexWarning
|
|||||||
var body = new StringBuilder();
|
var body = new StringBuilder();
|
||||||
|
|
||||||
// Counted rather than put into a plural form: the I18N has no mechanism for one.
|
// Counted rather than put into a plural form: the I18N has no mechanism for one.
|
||||||
body.AppendLine(string.Format(TB("This change makes the prepared documents of the following data sources unusable ({0}):"), affected.Count.CompactCount()));
|
body.AppendLineLF(string.Format(TB("This change makes the prepared documents of the following data sources unusable ({0}):"), affected.Count.CompactCount()));
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(FormatDataSourceNames(affected));
|
body.AppendLineLF(FormatDataSourceNames(affected));
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
|
body.AppendLineLF(TB("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
|
||||||
|
|
||||||
if (usesCloudEmbedding)
|
if (usesCloudEmbedding)
|
||||||
{
|
{
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Your embedding provider runs in the cloud, so preparing everything again costs money."));
|
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing everything again costs money."));
|
||||||
}
|
}
|
||||||
|
|
||||||
body.AppendLine();
|
body.AppendLineLF();
|
||||||
body.AppendLine(TB("Do you want to apply this change anyway?"));
|
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
|
||||||
|
|
||||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ public sealed class DocumentManager
|
|||||||
// headings, leaving the AI unable to tell the structure of the document apart from our
|
// headings, leaving the AI unable to tell the structure of the document apart from our
|
||||||
// boundaries. The presentation reader marks its slides the same way.
|
// boundaries. The presentation reader marks its slides the same way.
|
||||||
//
|
//
|
||||||
this.currentPageContent.AppendLine(metadata.Document?.PageIsExact is true ? $"<!-- Page {pageNumber} -->" : $"<!-- Estimated page {pageNumber} -->");
|
this.currentPageContent.AppendLineLF(metadata.Document?.PageIsExact is true ? $"<!-- Page {pageNumber} -->" : $"<!-- Estimated page {pageNumber} -->");
|
||||||
this.currentPageContent.AppendLine();
|
this.currentPageContent.AppendLineLF();
|
||||||
this.currentPageContent.Append(content);
|
this.currentPageContent.Append(content);
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -55,8 +55,8 @@ public sealed class DocumentManager
|
|||||||
var markdownImage = ContentStreamSseHandler.BuildImageMarkdown(image.Id, image.MediaType);
|
var markdownImage = ContentStreamSseHandler.BuildImageMarkdown(image.Id, image.MediaType);
|
||||||
if (markdownImage is not null)
|
if (markdownImage is not null)
|
||||||
{
|
{
|
||||||
this.currentPageContent.AppendLine();
|
this.currentPageContent.AppendLineLF();
|
||||||
this.currentPageContent.AppendLine(markdownImage);
|
this.currentPageContent.AppendLineLF(markdownImage);
|
||||||
|
|
||||||
//
|
//
|
||||||
// The runtime counted the text of this page, not the image we just embedded into it.
|
// The runtime counted the text of this page, not the image we just embedded into it.
|
||||||
|
|||||||
@@ -202,12 +202,12 @@ public static class FileExportFormatExtensions
|
|||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
case FileExportFormat.HTML or FileExportFormat.MARKDOWN:
|
case FileExportFormat.HTML or FileExportFormat.MARKDOWN:
|
||||||
var commentText = string.Join(Environment.NewLine, lines).Replace("-->", "-- >").Replace("--!>", "--! >");
|
var commentText = string.Join('\n', lines).Replace("-->", "-- >").Replace("--!>", "--! >");
|
||||||
comment = $"<!--{Environment.NewLine}{commentText}{Environment.NewLine}-->";
|
comment = $"<!--\n{commentText}\n-->";
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case FileExportFormat.LATEX:
|
case FileExportFormat.LATEX:
|
||||||
comment = string.Join(Environment.NewLine, lines.Select(line => line.Length is 0 ? "%" : $"% {line}"));
|
comment = string.Join('\n', lines.Select(line => line.Length is 0 ? "%" : $"% {line}"));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public static class Markdown
|
|||||||
if (document.Descendants<FencedCodeBlock>().LastOrDefault() is not { ClosingFencedCharCount: 0 } openFence)
|
if (document.Descendants<FencedCodeBlock>().LastOrDefault() is not { ClosingFencedCharCount: 0 } openFence)
|
||||||
return markdownText;
|
return markdownText;
|
||||||
|
|
||||||
return $"{markdownText}{Environment.NewLine}{new string(openFence.FencedChar, openFence.OpeningFencedCharCount)}";
|
return $"{markdownText}\n{new string(openFence.FencedChar, openFence.OpeningFencedCharCount)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Puts a block of Markdown below a Markdown text, such as its sources or a notice.</summary>
|
/// <summary>Puts a block of Markdown below a Markdown text, such as its sources or a notice.</summary>
|
||||||
@@ -118,7 +118,7 @@ public static class Markdown
|
|||||||
// The blank line is not cosmetic: it ends a paragraph, a list, a table, or a block quote, so
|
// The blank line is not cosmetic: it ends a paragraph, a list, a table, or a block quote, so
|
||||||
// that the block stands on its own instead of being pulled into the last block of the text.
|
// that the block stands on its own instead of being pulled into the last block of the text.
|
||||||
//
|
//
|
||||||
return $"{CloseOpenCodeFence(markdownText)}{Environment.NewLine}{Environment.NewLine}{block}";
|
return $"{CloseOpenCodeFence(markdownText)}\n\n{block}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RemoveSharedIndentation(string value)
|
public static string RemoveSharedIndentation(string value)
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public static class PlainFileExport
|
|||||||
/// Turns a formula into a file of LaTeX, between \[ and \] as a formula of its own, so that it can be
|
/// Turns a formula into a file of LaTeX, between \[ and \] as a formula of its own, so that it can be
|
||||||
/// pasted into a document as it is.
|
/// pasted into a document as it is.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static FoundFile ReadFormula(string formula) => new(string.Empty, FileExportFormat.LATEX, SourceCodeLanguage.NONE, $@"\[{Environment.NewLine}{formula}{Environment.NewLine}\]");
|
private static FoundFile ReadFormula(string formula) => new(string.Empty, FileExportFormat.LATEX, SourceCodeLanguage.NONE, $"\\[\n{formula}\n\\]");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Turns a Markdown table into a file.
|
/// Turns a Markdown table into a file.
|
||||||
@@ -130,7 +130,7 @@ public static class PlainFileExport
|
|||||||
|
|
||||||
var text = new StringBuilder();
|
var text = new StringBuilder();
|
||||||
foreach (var fields in rows)
|
foreach (var fields in rows)
|
||||||
text.AppendLine(CsvWriter.ToRow(separator, fields));
|
text.Append(CsvWriter.ToRow(separator, fields)).Append(CsvWriter.ROW_END);
|
||||||
|
|
||||||
return new(rows[0].FirstOrDefault() ?? string.Empty, FileExportFormat.CSV, SourceCodeLanguage.NONE, text.ToString());
|
return new(rows[0].FirstOrDefault() ?? string.Empty, FileExportFormat.CSV, SourceCodeLanguage.NONE, text.ToString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ internal sealed class AssistantDropdown : StatefulAssistantComponentBase
|
|||||||
public override string UserPromptFallback(AssistantState state)
|
public override string UserPromptFallback(AssistantState state)
|
||||||
{
|
{
|
||||||
if (this.IsMultiselect && state.MultiSelect.TryGetValue(this.Name, out var selections))
|
if (this.IsMultiselect && state.MultiSelect.TryGetValue(this.Name, out var selections))
|
||||||
return this.BuildAuditPromptBlock(string.Join(Environment.NewLine, selections.OrderBy(static value => value, StringComparer.Ordinal)));
|
return this.BuildAuditPromptBlock(string.Join('\n', selections.OrderBy(static value => value, StringComparer.Ordinal)));
|
||||||
|
|
||||||
state.SingleSelect.TryGetValue(this.Name, out var userInput);
|
state.SingleSelect.TryGetValue(this.Name, out var userInput);
|
||||||
return this.BuildAuditPromptBlock(userInput);
|
return this.BuildAuditPromptBlock(userInput);
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ internal sealed class AssistantFileAttachment : StatefulAssistantComponentBase
|
|||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
foreach (var attachment in fileState.DocumentPaths.OrderBy(static attachment => attachment.FilePath, StringComparer.Ordinal))
|
foreach (var attachment in fileState.DocumentPaths.OrderBy(static attachment => attachment.FilePath, StringComparer.Ordinal))
|
||||||
builder.AppendLine(attachment.FilePath);
|
builder.AppendLineLF(attachment.FilePath);
|
||||||
|
|
||||||
return this.BuildAuditPromptBlock(builder.ToString());
|
return this.BuildAuditPromptBlock(builder.ToString());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -282,14 +282,14 @@ internal static class AssistantLuaConversion
|
|||||||
var indent = new string(' ', depth * 2);
|
var indent = new string(' ', depth * 2);
|
||||||
var childIndent = new string(' ', (depth + 1) * 2);
|
var childIndent = new string(' ', (depth + 1) * 2);
|
||||||
var builder = new System.Text.StringBuilder();
|
var builder = new System.Text.StringBuilder();
|
||||||
builder.AppendLine("{");
|
builder.AppendLineLF("{");
|
||||||
|
|
||||||
foreach (var entry in table)
|
foreach (var entry in table)
|
||||||
{
|
{
|
||||||
builder.Append(childIndent);
|
builder.Append(childIndent);
|
||||||
builder.Append(FormatLuaValue(entry.Key));
|
builder.Append(FormatLuaValue(entry.Key));
|
||||||
builder.Append(" = ");
|
builder.Append(" = ");
|
||||||
builder.AppendLine(FormatLuaValue(entry.Value, depth + 1));
|
builder.AppendLineLF(FormatLuaValue(entry.Value, depth + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(indent);
|
builder.Append(indent);
|
||||||
|
|||||||
+7
-7
@@ -18,13 +18,13 @@ public abstract class StatefulAssistantComponentBase : NamedAssistantComponentBa
|
|||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
var fieldName = this.Type.ToString().ToLowerInvariant();
|
var fieldName = this.Type.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
builder.AppendLine($"[{fieldName}]");
|
builder.AppendLineLF($"[{fieldName}]");
|
||||||
builder.Append("name: ").AppendLine(this.Name);
|
builder.Append("name: ").AppendLineLF(this.Name);
|
||||||
builder.AppendLine("context:");
|
builder.AppendLineLF("context:");
|
||||||
builder.AppendLine(!string.IsNullOrEmpty(this.UserPrompt) ? this.UserPrompt : "<not provided>");
|
builder.AppendLineLF(!string.IsNullOrEmpty(this.UserPrompt) ? this.UserPrompt : "<not provided>");
|
||||||
builder.AppendLine("value:");
|
builder.AppendLineLF("value:");
|
||||||
builder.AppendLine(!string.IsNullOrEmpty(value) ? value : "<empty>");
|
builder.AppendLineLF(!string.IsNullOrEmpty(value) ? value : "<empty>");
|
||||||
builder.Append($"[/{fieldName}]").AppendLine().AppendLine();
|
builder.Append($"[/{fieldName}]").AppendLineLF().AppendLineLF();
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+67
-67
@@ -95,71 +95,71 @@ public static class DirectChatLauncherLuaWriter
|
|||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.AppendLine("--[[");
|
builder.AppendLineLF("--[[");
|
||||||
builder.AppendLine(" This direct chat launcher is maintained by AI Studio: its settings dialog rewrites this");
|
builder.AppendLineLF(" This direct chat launcher is maintained by AI Studio: its settings dialog rewrites this");
|
||||||
builder.AppendLine(" file as a whole. Editing it by hand works, but the next change made through the dialog");
|
builder.AppendLineLF(" file as a whole. Editing it by hand works, but the next change made through the dialog");
|
||||||
builder.AppendLine(" replaces everything below, including comments and formatting.");
|
builder.AppendLineLF(" replaces everything below, including comments and formatting.");
|
||||||
builder.AppendLine("]]");
|
builder.AppendLineLF("]]");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The ID for this plugin:");
|
builder.AppendLineLF("-- The ID for this plugin:");
|
||||||
builder.AppendLine($"ID = \"{plugin.Id}\"");
|
builder.AppendLineLF($"ID = \"{plugin.Id}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The name of the plugin:");
|
builder.AppendLineLF("-- The name of the plugin:");
|
||||||
builder.AppendLine($"NAME = \"{Escape(definition.PluginName)}\"");
|
builder.AppendLineLF($"NAME = \"{Escape(definition.PluginName)}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The description of the plugin:");
|
builder.AppendLineLF("-- The description of the plugin:");
|
||||||
builder.AppendLine($"DESCRIPTION = \"{Escape(definition.Description)}\"");
|
builder.AppendLineLF($"DESCRIPTION = \"{Escape(definition.Description)}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The version of the plugin:");
|
builder.AppendLineLF("-- The version of the plugin:");
|
||||||
builder.AppendLine($"VERSION = \"{plugin.Version}\"");
|
builder.AppendLineLF($"VERSION = \"{plugin.Version}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
if (plugin.LastChanged is { } lastChanged)
|
if (plugin.LastChanged is { } lastChanged)
|
||||||
{
|
{
|
||||||
builder.AppendLine("-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.");
|
builder.AppendLineLF("-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.");
|
||||||
builder.AppendLine($"LAST_CHANGED = \"{PluginBase.FormatLastChanged(lastChanged)}\"");
|
builder.AppendLineLF($"LAST_CHANGED = \"{PluginBase.FormatLastChanged(lastChanged)}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine("-- The type of the plugin:");
|
builder.AppendLineLF("-- The type of the plugin:");
|
||||||
builder.AppendLine($"TYPE = \"{nameof(PluginType.ASSISTANT)}\"");
|
builder.AppendLineLF($"TYPE = \"{nameof(PluginType.ASSISTANT)}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The authors of the plugin:");
|
builder.AppendLineLF("-- The authors of the plugin:");
|
||||||
builder.AppendLine($"AUTHORS = {WriteStringList(plugin.Authors, FALLBACK_AUTHOR)}");
|
builder.AppendLineLF($"AUTHORS = {WriteStringList(plugin.Authors, FALLBACK_AUTHOR)}");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The support contact for the plugin:");
|
builder.AppendLineLF("-- The support contact for the plugin:");
|
||||||
builder.AppendLine($"SUPPORT_CONTACT = \"{Escape(ValueOrFallback(plugin.SupportContact, FALLBACK_SUPPORT_CONTACT))}\"");
|
builder.AppendLineLF($"SUPPORT_CONTACT = \"{Escape(ValueOrFallback(plugin.SupportContact, FALLBACK_SUPPORT_CONTACT))}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The source URL for the plugin:");
|
builder.AppendLineLF("-- The source URL for the plugin:");
|
||||||
builder.AppendLine($"SOURCE_URL = \"{Escape(ValueOrFallback(plugin.SourceURL, FALLBACK_SOURCE_URL))}\"");
|
builder.AppendLineLF($"SOURCE_URL = \"{Escape(ValueOrFallback(plugin.SourceURL, FALLBACK_SOURCE_URL))}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The categories for the plugin:");
|
builder.AppendLineLF("-- The categories for the plugin:");
|
||||||
builder.AppendLine($"CATEGORIES = {WriteEnumList(plugin.Categories, FALLBACK_CATEGORY)}");
|
builder.AppendLineLF($"CATEGORIES = {WriteEnumList(plugin.Categories, FALLBACK_CATEGORY)}");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The target groups for the plugin:");
|
builder.AppendLineLF("-- The target groups for the plugin:");
|
||||||
builder.AppendLine($"TARGET_GROUPS = {WriteEnumList(plugin.TargetGroups, FALLBACK_TARGET_GROUP)}");
|
builder.AppendLineLF($"TARGET_GROUPS = {WriteEnumList(plugin.TargetGroups, FALLBACK_TARGET_GROUP)}");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- The flag for whether the plugin is maintained:");
|
builder.AppendLineLF("-- The flag for whether the plugin is maintained:");
|
||||||
builder.AppendLine($"IS_MAINTAINED = {WriteBoolean(plugin.IsMaintained)}");
|
builder.AppendLineLF($"IS_MAINTAINED = {WriteBoolean(plugin.IsMaintained)}");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- When the plugin is deprecated, this message will be shown to users:");
|
builder.AppendLineLF("-- When the plugin is deprecated, this message will be shown to users:");
|
||||||
builder.AppendLine($"DEPRECATION_MESSAGE = \"{Escape(plugin.DeprecationMessage)}\"");
|
builder.AppendLineLF($"DEPRECATION_MESSAGE = \"{Escape(plugin.DeprecationMessage)}\"");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
builder.AppendLine("-- Enterprise-managed assistants cannot be revised with AI. Keep false for locally managed plugins:");
|
builder.AppendLineLF("-- Enterprise-managed assistants cannot be revised with AI. Keep false for locally managed plugins:");
|
||||||
builder.AppendLine("DEPLOYED_USING_CONFIG_SERVER = false");
|
builder.AppendLineLF("DEPLOYED_USING_CONFIG_SERVER = false");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
//
|
//
|
||||||
// This metadata marks assistants the Builder created and must not appear on manually
|
// This metadata marks assistants the Builder created and must not appear on manually
|
||||||
@@ -167,26 +167,26 @@ public static class DirectChatLauncherLuaWriter
|
|||||||
//
|
//
|
||||||
if (plugin.IsAssistantBuilderGenerated)
|
if (plugin.IsAssistantBuilderGenerated)
|
||||||
{
|
{
|
||||||
builder.AppendLine("-- This assistant was created by the AI Studio Assistant Builder:");
|
builder.AppendLineLF("-- This assistant was created by the AI Studio Assistant Builder:");
|
||||||
builder.AppendLine("AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}");
|
builder.AppendLineLF("AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine("-- The tile opens a chat directly, hence it needs no system prompt, no submit text, and no UI:");
|
builder.AppendLineLF("-- The tile opens a chat directly, hence it needs no system prompt, no submit text, and no UI:");
|
||||||
builder.AppendLine("ASSISTANT = {");
|
builder.AppendLineLF("ASSISTANT = {");
|
||||||
builder.AppendLine($" [\"Title\"] = \"{Escape(definition.Title)}\",");
|
builder.AppendLineLF($" [\"Title\"] = \"{Escape(definition.Title)}\",");
|
||||||
builder.AppendLine($" [\"Description\"] = \"{Escape(definition.Description)}\",");
|
builder.AppendLineLF($" [\"Description\"] = \"{Escape(definition.Description)}\",");
|
||||||
//
|
//
|
||||||
// The behavior follows from the workspace rather than being tracked next to it. A launcher
|
// The behavior follows from the workspace rather than being tracked next to it. A launcher
|
||||||
// without one has no name to write, and the plugin loader rejects a WorkspaceName there, so
|
// without one has no name to write, and the plugin loader rejects a WorkspaceName there, so
|
||||||
// the field is left out the same way the optional IDs below are:
|
// the field is left out the same way the optional IDs below are:
|
||||||
//
|
//
|
||||||
if (definition.Launch.OpensTemporaryChat)
|
if (definition.Launch.OpensTemporaryChat)
|
||||||
builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT)}\",");
|
builder.AppendLineLF($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT)}\",");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\",");
|
builder.AppendLineLF($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\",");
|
||||||
builder.AppendLine($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\",");
|
builder.AppendLineLF($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\",");
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -194,30 +194,30 @@ public static class DirectChatLauncherLuaWriter
|
|||||||
// profile or no chat template. An empty provider GUID has no such meaning and is invalid:
|
// profile or no chat template. An empty provider GUID has no such meaning and is invalid:
|
||||||
//
|
//
|
||||||
if (definition.Launch.ProviderId is { } providerId && providerId != Guid.Empty)
|
if (definition.Launch.ProviderId is { } providerId && providerId != Guid.Empty)
|
||||||
builder.AppendLine($" [\"ProviderId\"] = \"{providerId}\",");
|
builder.AppendLineLF($" [\"ProviderId\"] = \"{providerId}\",");
|
||||||
|
|
||||||
if (definition.Launch.ProfileId is { } profileId)
|
if (definition.Launch.ProfileId is { } profileId)
|
||||||
builder.AppendLine($" [\"ProfileId\"] = \"{profileId}\",");
|
builder.AppendLineLF($" [\"ProfileId\"] = \"{profileId}\",");
|
||||||
|
|
||||||
if (definition.Launch.ChatTemplateId is { } chatTemplateId)
|
if (definition.Launch.ChatTemplateId is { } chatTemplateId)
|
||||||
builder.AppendLine($" [\"ChatTemplateId\"] = \"{chatTemplateId}\",");
|
builder.AppendLineLF($" [\"ChatTemplateId\"] = \"{chatTemplateId}\",");
|
||||||
|
|
||||||
if (definition.Launch.DataSourceIds is { Count: > 0 } dataSourceIds)
|
if (definition.Launch.DataSourceIds is { Count: > 0 } dataSourceIds)
|
||||||
{
|
{
|
||||||
builder.AppendLine(" [\"DataSourceIds\"] = {");
|
builder.AppendLineLF(" [\"DataSourceIds\"] = {");
|
||||||
foreach (var dataSourceId in dataSourceIds)
|
foreach (var dataSourceId in dataSourceIds)
|
||||||
builder.AppendLine($" \"{dataSourceId}\",");
|
builder.AppendLineLF($" \"{dataSourceId}\",");
|
||||||
|
|
||||||
builder.AppendLine(" },");
|
builder.AppendLineLF(" },");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.Launch.ToolIds is { Count: > 0 } toolIds)
|
if (definition.Launch.ToolIds is { Count: > 0 } toolIds)
|
||||||
{
|
{
|
||||||
builder.AppendLine(" [\"ToolIds\"] = {");
|
builder.AppendLineLF(" [\"ToolIds\"] = {");
|
||||||
foreach (var toolId in toolIds)
|
foreach (var toolId in toolIds)
|
||||||
builder.AppendLine($" {LuaTools.ToLuaStringLiteral(toolId)},");
|
builder.AppendLineLF($" {LuaTools.ToLuaStringLiteral(toolId)},");
|
||||||
|
|
||||||
builder.AppendLine(" },");
|
builder.AppendLineLF(" },");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append('}');
|
builder.Append('}');
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
|||||||
|
|
||||||
private static string BuildSecureSystemPrompt(string pluginSystemPrompt)
|
private static string BuildSecureSystemPrompt(string pluginSystemPrompt)
|
||||||
{
|
{
|
||||||
var separator = $"{Environment.NewLine}{Environment.NewLine}";
|
var separator = "\n\n";
|
||||||
return string.IsNullOrWhiteSpace(pluginSystemPrompt) ? $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}" : $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{pluginSystemPrompt.Trim()}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}";
|
return string.IsNullOrWhiteSpace(pluginSystemPrompt) ? $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}" : $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{pluginSystemPrompt.Trim()}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -939,7 +939,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
|||||||
builder.Append(string.IsNullOrWhiteSpace(stateful.UserPrompt) ? "empty" : "set");
|
builder.Append(string.IsNullOrWhiteSpace(stateful.UserPrompt) ? "empty" : "set");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
if (component.Children.Count > 0)
|
if (component.Children.Count > 0)
|
||||||
AppendComponentSummary(builder, component.Children, depth + 1);
|
AppendComponentSummary(builder, component.Children, depth + 1);
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ public sealed class AugmentationOne : IAugmentationProcess
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (selection.Contexts.Count > 0)
|
if (selection.Contexts.Count > 0)
|
||||||
{
|
{
|
||||||
sb.AppendLine("The following useful information will help you in processing the user prompt:");
|
sb.AppendLineLF("The following useful information will help you in processing the user prompt:");
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
|
|
||||||
// Let's convert the retrieval contexts to Markdown:
|
// Let's convert the retrieval contexts to Markdown:
|
||||||
for (var index = 0; index < selection.Contexts.Count; index++)
|
for (var index = 0; index < selection.Contexts.Count; index++)
|
||||||
@@ -147,7 +147,7 @@ public sealed class AugmentationOne : IAugmentationProcess
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selection.LeftOutCount > 0)
|
if (selection.LeftOutCount > 0)
|
||||||
sb.AppendLine(selection.LeftOutCount is 1
|
sb.AppendLineLF(selection.LeftOutCount is 1
|
||||||
? "Of the passages which the search found in the data sources, 1 did not fit into this request and was left out. An answer based on the data sources may therefore be incomplete."
|
? "Of the passages which the search found in the data sources, 1 did not fit into this request and was left out. An answer based on the data sources may therefore be incomplete."
|
||||||
: $"Of the passages which the search found in the data sources, {selection.LeftOutCount} did not fit into this request and were left out. An answer based on the data sources may therefore be incomplete.");
|
: $"Of the passages which the search found in the data sources, {selection.LeftOutCount} did not fit into this request and were left out. An answer based on the data sources may therefore be incomplete.");
|
||||||
|
|
||||||
|
|||||||
@@ -29,20 +29,20 @@ public sealed record PreparedRetrievalContext(IRetrievalContext Context, string
|
|||||||
switch (index)
|
switch (index)
|
||||||
{
|
{
|
||||||
case > 0 when numTotalRetrievalContexts is null:
|
case > 0 when numTotalRetrievalContexts is null:
|
||||||
sb.AppendLine($"# Retrieval context {index}");
|
sb.AppendLineLF($"# Retrieval context {index}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case > 0 when numTotalRetrievalContexts > 0:
|
case > 0 when numTotalRetrievalContexts > 0:
|
||||||
sb.AppendLine($"# Retrieval context {index} of {numTotalRetrievalContexts}");
|
sb.AppendLineLF($"# Retrieval context {index} of {numTotalRetrievalContexts}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sb.AppendLine("# Retrieval context");
|
sb.AppendLineLF("# Retrieval context");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Append(this.Body);
|
sb.Append(this.Body);
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,7 +330,7 @@ public sealed class AISrcSelWithRetCtxVal : IRagProcess
|
|||||||
|
|
||||||
var builder = new StringBuilder(augmentedData);
|
var builder = new StringBuilder(augmentedData);
|
||||||
if (builder.Length > 0)
|
if (builder.Length > 0)
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
UnreachableDataSources.AppendTo(builder, unreachableDataSources);
|
UnreachableDataSources.AppendTo(builder, unreachableDataSources);
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
|
|||||||
@@ -23,23 +23,23 @@ public static class RetrievalContextExtensions
|
|||||||
/// <param name="retrievalContext">The context to describe.</param>
|
/// <param name="retrievalContext">The context to describe.</param>
|
||||||
internal static void AppendContextDescription(StringBuilder contextBuilder, IRetrievalContext retrievalContext)
|
internal static void AppendContextDescription(StringBuilder contextBuilder, IRetrievalContext retrievalContext)
|
||||||
{
|
{
|
||||||
contextBuilder.AppendLine($"Data source name: {retrievalContext.DataSourceName}");
|
contextBuilder.AppendLineLF($"Data source name: {retrievalContext.DataSourceName}");
|
||||||
contextBuilder.AppendLine($"Content category: {retrievalContext.Category}");
|
contextBuilder.AppendLineLF($"Content category: {retrievalContext.Category}");
|
||||||
if(!string.IsNullOrWhiteSpace(retrievalContext.SourceCategory))
|
if(!string.IsNullOrWhiteSpace(retrievalContext.SourceCategory))
|
||||||
contextBuilder.AppendLine($"Source category: {retrievalContext.SourceCategory.ReplaceLineEndings(" ").Trim()}");
|
contextBuilder.AppendLineLF($"Source category: {retrievalContext.SourceCategory.ReplaceLineEndings(" ").Trim()}");
|
||||||
|
|
||||||
contextBuilder.AppendLine($"Content type: {retrievalContext.Type}");
|
contextBuilder.AppendLineLF($"Content type: {retrievalContext.Type}");
|
||||||
contextBuilder.AppendLine($"Content path: {retrievalContext.Path}");
|
contextBuilder.AppendLineLF($"Content path: {retrievalContext.Path}");
|
||||||
|
|
||||||
if(retrievalContext is RetrievalTextContext textContext && DescribeLocation(textContext.PageNumber, textContext.EndPageNumber, textContext.SheetName, textContext.StartRow, textContext.EndRow, textContext.StartLine, textContext.EndLine) is { } location)
|
if(retrievalContext is RetrievalTextContext textContext && DescribeLocation(textContext.PageNumber, textContext.EndPageNumber, textContext.SheetName, textContext.StartRow, textContext.EndRow, textContext.StartLine, textContext.EndLine) is { } location)
|
||||||
contextBuilder.AppendLine($"Content location: {location}");
|
contextBuilder.AppendLineLF($"Content location: {location}");
|
||||||
|
|
||||||
if(retrievalContext.Links.Count is 0)
|
if(retrievalContext.Links.Count is 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
contextBuilder.AppendLine("Additional links:");
|
contextBuilder.AppendLineLF("Additional links:");
|
||||||
foreach(var link in retrievalContext.Links)
|
foreach(var link in retrievalContext.Links)
|
||||||
contextBuilder.AppendLine($"- {link}");
|
contextBuilder.AppendLineLF($"- {link}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,8 +86,8 @@ public static class RetrievalContextExtensions
|
|||||||
/// <param name="contextBuilder">The builder to write into, after the description of the context.</param>
|
/// <param name="contextBuilder">The builder to write into, after the description of the context.</param>
|
||||||
internal static void AppendImageMatch(StringBuilder contextBuilder)
|
internal static void AppendImageMatch(StringBuilder contextBuilder)
|
||||||
{
|
{
|
||||||
contextBuilder.AppendLine();
|
contextBuilder.AppendLineLF();
|
||||||
contextBuilder.AppendLine("Matched image content: an image matched here, but it is not shown to you. Do not guess what it shows. When it matters for the answer, tell the user that an image matched, and where to find it, using the path and the links above.");
|
contextBuilder.AppendLineLF("Matched image content: an image matched here, but it is not shown to you. Do not guess what it shows. When it matters for the answer, tell the user that an image matched, and where to find it, using the path and the links above.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <param name="retrievalContexts">The contexts to prepare.</param>
|
/// <param name="retrievalContexts">The contexts to prepare.</param>
|
||||||
@@ -295,31 +295,31 @@ public static class RetrievalContextExtensions
|
|||||||
/// <param name="surroundingContent">The surrounding content, filtered.</param>
|
/// <param name="surroundingContent">The surrounding content, filtered.</param>
|
||||||
private static void AppendTextMatch(StringBuilder contextBuilder, RetrievalTextContext textContext, string matchedText, IReadOnlyList<string> surroundingContent)
|
private static void AppendTextMatch(StringBuilder contextBuilder, RetrievalTextContext textContext, string matchedText, IReadOnlyList<string> surroundingContent)
|
||||||
{
|
{
|
||||||
contextBuilder.AppendLine();
|
contextBuilder.AppendLineLF();
|
||||||
contextBuilder.AppendLine(textContext.IsMatchedTextShortened ? "Matched text content, shortened to fit the size limit:" : "Matched text content:");
|
contextBuilder.AppendLineLF(textContext.IsMatchedTextShortened ? "Matched text content, shortened to fit the size limit:" : "Matched text content:");
|
||||||
contextBuilder.AppendLine("````");
|
contextBuilder.AppendLineLF("````");
|
||||||
contextBuilder.AppendLine(matchedText);
|
contextBuilder.AppendLineLF(matchedText);
|
||||||
contextBuilder.AppendLine("````");
|
contextBuilder.AppendLineLF("````");
|
||||||
|
|
||||||
if (surroundingContent.Count is 0)
|
if (surroundingContent.Count is 0)
|
||||||
{
|
{
|
||||||
if (textContext.IsSurroundingContentShortened)
|
if (textContext.IsSurroundingContentShortened)
|
||||||
{
|
{
|
||||||
contextBuilder.AppendLine();
|
contextBuilder.AppendLineLF();
|
||||||
contextBuilder.AppendLine("Surrounding text content: left out to fit the size limit.");
|
contextBuilder.AppendLineLF("Surrounding text content: left out to fit the size limit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contextBuilder.AppendLine();
|
contextBuilder.AppendLineLF();
|
||||||
contextBuilder.AppendLine(textContext.IsSurroundingContentShortened ? "Surrounding text content, shortened to fit the size limit:" : "Surrounding text content:");
|
contextBuilder.AppendLineLF(textContext.IsSurroundingContentShortened ? "Surrounding text content, shortened to fit the size limit:" : "Surrounding text content:");
|
||||||
foreach (var surrounding in surroundingContent)
|
foreach (var surrounding in surroundingContent)
|
||||||
{
|
{
|
||||||
contextBuilder.AppendLine();
|
contextBuilder.AppendLineLF();
|
||||||
contextBuilder.AppendLine("````");
|
contextBuilder.AppendLineLF("````");
|
||||||
contextBuilder.AppendLine(surrounding);
|
contextBuilder.AppendLineLF(surrounding);
|
||||||
contextBuilder.AppendLine("````");
|
contextBuilder.AppendLineLF("````");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ public static class UnreachableDataSources
|
|||||||
/// <param name="dataSources">The data sources whose server could not be reached, in the order to list them.</param>
|
/// <param name="dataSources">The data sources whose server could not be reached, in the order to list them.</param>
|
||||||
public static void AppendTo(StringBuilder builder, IReadOnlyList<IDataSource> dataSources)
|
public static void AppendTo(StringBuilder builder, IReadOnlyList<IDataSource> dataSources)
|
||||||
{
|
{
|
||||||
builder.AppendLine("These external data sources of this chat could not be reached or signed in to right now, so they cannot be searched. Their content is missing rather than empty; tell the user so when a question concerns them:");
|
builder.AppendLineLF("These external data sources of this chat could not be reached or signed in to right now, so they cannot be searched. Their content is missing rather than empty; tell the user so when a question concerns them:");
|
||||||
foreach (var dataSource in dataSources)
|
foreach (var dataSource in dataSources)
|
||||||
builder.AppendLine($"- name='{dataSource.Name}'");
|
builder.AppendLineLF($"- name='{dataSource.Name}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,24 +261,24 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine($"# {contextFile.Title}");
|
builder.AppendLineLF($"# {contextFile.Title}");
|
||||||
builder.AppendLine($"Source: {contextFile.RelativePath}");
|
builder.AppendLineLF($"Source: {contextFile.RelativePath}");
|
||||||
builder.AppendLine("<context>");
|
builder.AppendLineLF("<context>");
|
||||||
builder.AppendLine(content.Trim());
|
builder.AppendLineLF(content.Trim());
|
||||||
builder.AppendLine("</context>");
|
builder.AppendLineLF("</context>");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Unlike the files above, this list is not the same on two installations. It is the only
|
// Unlike the files above, this list is not the same on two installations. It is the only
|
||||||
// place the model learns which tool IDs exist, so an assistant cannot name a tool without it:
|
// place the model learns which tool IDs exist, so an assistant cannot name a tool without it:
|
||||||
//
|
//
|
||||||
builder.AppendLine("# Available tools");
|
builder.AppendLineLF("# Available tools");
|
||||||
builder.AppendLine("Source: the tools installed in this AI Studio");
|
builder.AppendLineLF("Source: the tools installed in this AI Studio");
|
||||||
builder.AppendLine("<context>");
|
builder.AppendLineLF("<context>");
|
||||||
builder.AppendLine(await this.FormatAvailableToolsAsync());
|
builder.AppendLineLF(await this.FormatAvailableToolsAsync());
|
||||||
builder.AppendLine("</context>");
|
builder.AppendLineLF("</context>");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
|
|
||||||
return builder.ToString().Trim();
|
return builder.ToString().Trim();
|
||||||
}
|
}
|
||||||
@@ -302,7 +302,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
|||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
foreach (var tool in activeTools)
|
foreach (var tool in activeTools)
|
||||||
builder.AppendLine($"- {tool.Id}: {tool.DescriptionForLLM}");
|
builder.AppendLineLF($"- {tool.Id}: {tool.DescriptionForLLM}");
|
||||||
|
|
||||||
return builder.ToString().TrimEnd();
|
return builder.ToString().TrimEnd();
|
||||||
}
|
}
|
||||||
@@ -729,11 +729,11 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
|||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
foreach (var (relativePath, content) in luaFiles)
|
foreach (var (relativePath, content) in luaFiles)
|
||||||
{
|
{
|
||||||
builder.AppendLine($"# {relativePath}");
|
builder.AppendLineLF($"# {relativePath}");
|
||||||
builder.AppendLine("```lua");
|
builder.AppendLineLF("```lua");
|
||||||
builder.AppendLine(content.Trim());
|
builder.AppendLineLF(content.Trim());
|
||||||
builder.AppendLine("```");
|
builder.AppendLineLF("```");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString().Trim();
|
return builder.ToString().Trim();
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ public sealed partial class RustService
|
|||||||
if (content.Length >= maxCharacters)
|
if (content.Length >= maxCharacters)
|
||||||
return string.IsNullOrWhiteSpace(piece);
|
return string.IsNullOrWhiteSpace(piece);
|
||||||
|
|
||||||
content.AppendLine(piece);
|
content.AppendLineLF(piece);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ public sealed class SlideManager
|
|||||||
//
|
//
|
||||||
|
|
||||||
var contentBuilder = new StringBuilder();
|
var contentBuilder = new StringBuilder();
|
||||||
contentBuilder.AppendLine();
|
contentBuilder.AppendLineLF();
|
||||||
contentBuilder.AppendLine($"# Slide {slideNumber}");
|
contentBuilder.AppendLineLF($"# Slide {slideNumber}");
|
||||||
|
|
||||||
// Add any text content to the slide?
|
// Add any text content to the slide?
|
||||||
if(!string.IsNullOrWhiteSpace(content))
|
if(!string.IsNullOrWhiteSpace(content))
|
||||||
contentBuilder.AppendLine(content);
|
contentBuilder.AppendLineLF(content);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Add the text content to the slide:
|
// Add the text content to the slide:
|
||||||
@@ -78,7 +78,7 @@ public sealed class SlideManager
|
|||||||
if (!string.IsNullOrWhiteSpace(content))
|
if (!string.IsNullOrWhiteSpace(content))
|
||||||
{
|
{
|
||||||
var textContent = slide.Content.OfType<SlideTextContent>().First();
|
var textContent = slide.Content.OfType<SlideTextContent>().First();
|
||||||
textContent.Text.AppendLine(content);
|
textContent.Text.AppendLineLF(content);
|
||||||
slide.TokenCount = ContentStreamPendingContent.AddTokenCounts(slide.TokenCount, tokenCount);
|
slide.TokenCount = ContentStreamPendingContent.AddTokenCounts(slide.TokenCount, tokenCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,14 +112,14 @@ public sealed class SlideManager
|
|||||||
|
|
||||||
foreach (var text in slide.Content.OfType<SlideTextContent>())
|
foreach (var text in slide.Content.OfType<SlideTextContent>())
|
||||||
{
|
{
|
||||||
content.AppendLine(text.Text.ToString());
|
content.AppendLineLF(text.Text.ToString());
|
||||||
content.AppendLine();
|
content.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var image in slide.Content.OfType<SlideImageContent>())
|
foreach (var image in slide.Content.OfType<SlideImageContent>())
|
||||||
{
|
{
|
||||||
content.AppendLine(image.MarkdownImage);
|
content.AppendLineLF(image.MarkdownImage);
|
||||||
content.AppendLine();
|
content.AppendLineLF();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,10 +226,10 @@ public static partial class SourceExtensions
|
|||||||
foreach (var group in sources.GroupSources())
|
foreach (var group in sources.GroupSources())
|
||||||
{
|
{
|
||||||
if (sb.Length > 0)
|
if (sb.Length > 0)
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
|
|
||||||
sb.Append("## ");
|
sb.Append("## ");
|
||||||
sb.AppendLine(group.Heading);
|
sb.AppendLineLF(group.Heading);
|
||||||
|
|
||||||
foreach (var numberedSource in group.Sources)
|
foreach (var numberedSource in group.Sources)
|
||||||
{
|
{
|
||||||
@@ -241,7 +241,7 @@ public static partial class SourceExtensions
|
|||||||
else
|
else
|
||||||
AppendMarkdownLink(sb, numberedSource.Source.Title, keepPageAnchors ? numberedSource.Source.URL : WithoutPageAnchor(numberedSource.Source.URL));
|
AppendMarkdownLink(sb, numberedSource.Source.Title, keepPageAnchors ? numberedSource.Source.URL : WithoutPageAnchor(numberedSource.Source.URL));
|
||||||
|
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ public static partial class SourceExtensions
|
|||||||
if (string.IsNullOrWhiteSpace(sourcesMarkdown))
|
if (string.IsNullOrWhiteSpace(sourcesMarkdown))
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
return $"# {TB("Sources")}{Environment.NewLine}{Environment.NewLine}{sourcesMarkdown}";
|
return $"# {TB("Sources")}\n\n{sourcesMarkdown}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
+4
-4
@@ -89,11 +89,11 @@ public sealed class CountMailsTool(SettingsManager settingsManager, MailboxRetri
|
|||||||
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
|
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
|
||||||
{
|
{
|
||||||
var description = new StringBuilder(DESCRIPTION);
|
var description = new StringBuilder(DESCRIPTION);
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine($"The mailboxes you may count, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
|
description.AppendLineLF($"The mailboxes you may count, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
|
||||||
foreach (var mailbox in mailboxes)
|
foreach (var mailbox in mailboxes)
|
||||||
description.AppendLine($"- id={mailbox.Id}, name='{mailbox.Name}'");
|
description.AppendLineLF($"- id={mailbox.Id}, name='{mailbox.Name}'");
|
||||||
|
|
||||||
return function with
|
return function with
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-4
@@ -54,9 +54,9 @@ internal static class MailToolResults
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(text))
|
if (!string.IsNullOrWhiteSpace(text))
|
||||||
{
|
{
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
builder.AppendLine("---");
|
builder.AppendLineLF("---");
|
||||||
builder.AppendLine();
|
builder.AppendLineLF();
|
||||||
builder.Append(text);
|
builder.Append(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ internal static class MailToolResults
|
|||||||
void AppendField(string label, string? value)
|
void AppendField(string label, string? value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(value))
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
builder.AppendLine($"- **{label}:** {value}");
|
builder.AppendLineLF($"- **{label}:** {value}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -135,11 +135,11 @@ public sealed class SearchMailsTool(SettingsManager settingsManager, MailboxRetr
|
|||||||
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
|
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
|
||||||
{
|
{
|
||||||
var description = new StringBuilder(DESCRIPTION);
|
var description = new StringBuilder(DESCRIPTION);
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine($"The mailboxes you may search, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
|
description.AppendLineLF($"The mailboxes you may search, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
|
||||||
foreach (var mailbox in mailboxes)
|
foreach (var mailbox in mailboxes)
|
||||||
description.AppendLine($"- id={mailbox.Id}, name='{mailbox.Name}'");
|
description.AppendLineLF($"- id={mailbox.Id}, name='{mailbox.Name}'");
|
||||||
|
|
||||||
return function with
|
return function with
|
||||||
{
|
{
|
||||||
|
|||||||
+5
-5
@@ -286,21 +286,21 @@ public sealed class SemanticSearchTool(SettingsManager settingsManager, DataSour
|
|||||||
internal static ToolFunctionDefinition DescribeDataSources(ToolFunctionDefinition function, IReadOnlyList<(IDataSource DataSource, string Description)> dataSources, IReadOnlyList<IDataSource>? unreachableDataSources = null)
|
internal static ToolFunctionDefinition DescribeDataSources(ToolFunctionDefinition function, IReadOnlyList<(IDataSource DataSource, string Description)> dataSources, IReadOnlyList<IDataSource>? unreachableDataSources = null)
|
||||||
{
|
{
|
||||||
var description = new StringBuilder(DESCRIPTION);
|
var description = new StringBuilder(DESCRIPTION);
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
description.AppendLine($"The data sources you may search, by the ID to pass in {DATA_SOURCE_IDS_ARGUMENT}:");
|
description.AppendLineLF($"The data sources you may search, by the ID to pass in {DATA_SOURCE_IDS_ARGUMENT}:");
|
||||||
foreach (var (dataSource, dataSourceDescription) in dataSources)
|
foreach (var (dataSource, dataSourceDescription) in dataSources)
|
||||||
{
|
{
|
||||||
description.Append($"- id={dataSource.Id}, name='{dataSource.Name}', type={GetKind(dataSource)}");
|
description.Append($"- id={dataSource.Id}, name='{dataSource.Name}', type={GetKind(dataSource)}");
|
||||||
if (!string.IsNullOrWhiteSpace(dataSourceDescription))
|
if (!string.IsNullOrWhiteSpace(dataSourceDescription))
|
||||||
description.Append($", description='{dataSourceDescription.Trim().Shorten(MAX_DESCRIPTION_CHARACTERS)}'");
|
description.Append($", description='{dataSourceDescription.Trim().Shorten(MAX_DESCRIPTION_CHARACTERS)}'");
|
||||||
|
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unreachableDataSources is { Count: > 0 })
|
if (unreachableDataSources is { Count: > 0 })
|
||||||
{
|
{
|
||||||
description.AppendLine();
|
description.AppendLineLF();
|
||||||
UnreachableDataSources.AppendTo(description, unreachableDataSources);
|
UnreachableDataSources.AppendTo(description, unreachableDataSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public static class ToolSelectionRules
|
|||||||
public static string BuildToolPolicyPrompt(IEnumerable<ToolDefinition> definitions)
|
public static string BuildToolPolicyPrompt(IEnumerable<ToolDefinition> definitions)
|
||||||
{
|
{
|
||||||
var policySections = GetInstructedTools(definitions)
|
var policySections = GetInstructedTools(definitions)
|
||||||
.Select(x => $"## Tool `{x.Function.Name}`{Environment.NewLine}{x.SystemPromptInstructions.Trim()}")
|
.Select(x => $"## Tool `{x.Function.Name}`\n{x.SystemPromptInstructions.Trim()}")
|
||||||
.Distinct(StringComparer.Ordinal)
|
.Distinct(StringComparer.Ordinal)
|
||||||
.ToList();
|
.ToList();
|
||||||
if (policySections.Count == 0)
|
if (policySections.Count == 0)
|
||||||
@@ -60,7 +60,7 @@ public static class ToolSelectionRules
|
|||||||
# Tool usage instructions:
|
# Tool usage instructions:
|
||||||
You have multiple tools available. Each tool has a different purpose and usage policy. Choose wisely and if you are not sure, always ask the user for clarification. You must follow the usage policy of each tool to ensure accurate and reliable results. Here are the usage policies for each tool:
|
You have multiple tools available. Each tool has a different purpose and usage policy. Choose wisely and if you are not sure, always ask the user for clarification. You must follow the usage policy of each tool to ensure accurate and reliable results. Here are the usage policies for each tool:
|
||||||
|
|
||||||
{string.Join(Environment.NewLine+Environment.NewLine, policySections)}
|
{string.Join("\n\n", policySections)}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
return toolPolicyPrompt;
|
return toolPolicyPrompt;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public sealed partial class ToolSettingsService
|
|||||||
// dictionary rather than this tool's entry. A comment says the same thing without
|
// dictionary rather than this tool's entry. A comment says the same thing without
|
||||||
// overwriting anything:
|
// overwriting anything:
|
||||||
//
|
//
|
||||||
lua.AppendLine($"-- The whole table is locked unless you set CONFIG[\"SETTINGS\"][\"{MINIMUM_CONFIDENCE}.AllowUserOverride\"] = true");
|
lua.AppendLineLF($"-- The whole table is locked unless you set CONFIG[\"SETTINGS\"][\"{MINIMUM_CONFIDENCE}.AllowUserOverride\"] = true");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(LuaCode: lua.ToString());
|
return new(LuaCode: lua.ToString());
|
||||||
@@ -177,9 +177,9 @@ public sealed partial class ToolSettingsService
|
|||||||
|
|
||||||
var table = $"CONFIG[\"SETTINGS\"][\"{settingName}\"]";
|
var table = $"CONFIG[\"SETTINGS\"][\"{settingName}\"]";
|
||||||
if (lua.Length > 0)
|
if (lua.Length > 0)
|
||||||
lua.AppendLine();
|
lua.AppendLineLF();
|
||||||
lua.AppendLine($"{table} = {table} or {{}}");
|
lua.AppendLineLF($"{table} = {table} or {{}}");
|
||||||
foreach (var (key, value) in values)
|
foreach (var (key, value) in values)
|
||||||
lua.AppendLine($"{table}[\"{LuaTools.EscapeLuaString(key)}\"] = \"{LuaTools.EscapeLuaString(value)}\"");
|
lua.AppendLineLF($"{table}[\"{LuaTools.EscapeLuaString(key)}\"] = \"{LuaTools.EscapeLuaString(value)}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using SharedTools.Text;
|
||||||
|
|
||||||
namespace SharedTools;
|
namespace SharedTools;
|
||||||
|
|
||||||
public static class LuaTable
|
public static class LuaTable
|
||||||
@@ -9,7 +11,7 @@ public static class LuaTable
|
|||||||
//
|
//
|
||||||
// Add the UI_TEXT_CONTENT table:
|
// Add the UI_TEXT_CONTENT table:
|
||||||
//
|
//
|
||||||
sb.AppendLine($$"""{{tableVariableName}} = {}""");
|
sb.AppendLineLF($$"""{{tableVariableName}} = {}""");
|
||||||
foreach (var kvp in keyValuePairs.OrderBy(x => x.Key))
|
foreach (var kvp in keyValuePairs.OrderBy(x => x.Key))
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
@@ -32,11 +34,11 @@ public static class LuaTable
|
|||||||
keyWithoutPrefix = keyWithoutPrefix.Replace(".", "::");
|
keyWithoutPrefix = keyWithoutPrefix.Replace(".", "::");
|
||||||
|
|
||||||
// Add a comment with the original text content:
|
// Add a comment with the original text content:
|
||||||
sb.AppendLine();
|
sb.AppendLineLF();
|
||||||
sb.AppendLine($"-- {commentValue}");
|
sb.AppendLineLF($"-- {commentValue}");
|
||||||
|
|
||||||
// Add the assignment to the UI_TEXT_CONTENT table:
|
// Add the assignment to the UI_TEXT_CONTENT table:
|
||||||
sb.AppendLine($"""
|
sb.AppendLineLF($"""
|
||||||
UI_TEXT_CONTENT["{keyWithoutPrefix}"] = "{LuaTools.EscapeLuaString(value)}"
|
UI_TEXT_CONTENT["{keyWithoutPrefix}"] = "{LuaTools.EscapeLuaString(value)}"
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SharedTools;
|
namespace SharedTools.Text;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends lines with LF, the same line break on every system.
|
/// Appends lines with LF, the same line break on every system.
|
||||||
@@ -21,3 +21,4 @@
|
|||||||
MWAIS0015 | Usage | Error | JsonSourceGenerationAnalyzer
|
MWAIS0015 | Usage | Error | JsonSourceGenerationAnalyzer
|
||||||
MWAIS0016 | Style | Error | ClassicExtensionMethodAnalyzer
|
MWAIS0016 | Style | Error | ClassicExtensionMethodAnalyzer
|
||||||
MWAIS0017 | Usage | Error | IconCatalogAnalyzer
|
MWAIS0017 | Usage | Error | IconCatalogAnalyzer
|
||||||
|
MWAIS0018 | Usage | Error | SystemLineBreakAnalyzer
|
||||||
@@ -19,4 +19,5 @@ public static class Identifier
|
|||||||
public const string JSON_SOURCE_GENERATION_ANALYZER = $"{Tools.ID_PREFIX}0015";
|
public const string JSON_SOURCE_GENERATION_ANALYZER = $"{Tools.ID_PREFIX}0015";
|
||||||
public const string CLASSIC_EXTENSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0016";
|
public const string CLASSIC_EXTENSION_METHOD_ANALYZER = $"{Tools.ID_PREFIX}0016";
|
||||||
public const string ICON_CATALOG_ANALYZER = $"{Tools.ID_PREFIX}0017";
|
public const string ICON_CATALOG_ANALYZER = $"{Tools.ID_PREFIX}0017";
|
||||||
|
public const string SYSTEM_LINE_BREAK_ANALYZER = $"{Tools.ID_PREFIX}0018";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
|
namespace SourceCodeRules.UsageAnalyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refuses the line break of the system: StringBuilder.AppendLine and Environment.NewLine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Both write CR LF on Windows and LF everywhere else. A text built with them differs between the systems, it mixes
|
||||||
|
/// two kinds of line breaks as soon as one of its parts comes with LF, and a test which expects LF fails on Windows
|
||||||
|
/// only, where no pipeline runs the tests. AppendLineLF of SharedTools.Text and '\n' write LF on every system. Where a
|
||||||
|
/// format needs CR LF, such as a row of a CSV file, write it on purpose, as CsvWriter.ROW_END does.
|
||||||
|
/// </remarks>
|
||||||
|
#pragma warning disable RS1038
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
#pragma warning restore RS1038
|
||||||
|
public sealed class SystemLineBreakAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The key of the properties of a diagnostic which tells the code fix what was found.
|
||||||
|
/// </summary>
|
||||||
|
public const string KIND_KEY = "Kind";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The kind of a diagnostic about a call of StringBuilder.AppendLine.
|
||||||
|
/// </summary>
|
||||||
|
public const string KIND_APPEND_LINE = "AppendLine";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The kind of a diagnostic about Environment.NewLine.
|
||||||
|
/// </summary>
|
||||||
|
public const string KIND_NEW_LINE = "NewLine";
|
||||||
|
|
||||||
|
private const string DIAGNOSTIC_ID = Identifier.SYSTEM_LINE_BREAK_ANALYZER;
|
||||||
|
|
||||||
|
private const string TITLE = "Line breaks are LF on every system";
|
||||||
|
|
||||||
|
private const string MESSAGE_FORMAT = "'{0}' writes the line break of the system, which is CR LF on Windows; use {1} instead";
|
||||||
|
|
||||||
|
private const string DESCRIPTION = "StringBuilder.AppendLine and Environment.NewLine write CR LF on Windows and LF elsewhere, so a text differs between the systems and mixes two kinds of line breaks. Use AppendLineLF of SharedTools.Text or '\\n'.";
|
||||||
|
|
||||||
|
private const string CATEGORY = "Usage";
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor RULE = new(DIAGNOSTIC_ID, TITLE, MESSAGE_FORMAT, CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true, description: DESCRIPTION);
|
||||||
|
|
||||||
|
private static readonly ImmutableDictionary<string, string?> APPEND_LINE_PROPERTIES = ImmutableDictionary<string, string?>.Empty.Add(KIND_KEY, KIND_APPEND_LINE);
|
||||||
|
|
||||||
|
private static readonly ImmutableDictionary<string, string?> NEW_LINE_PROPERTIES = ImmutableDictionary<string, string?>.Empty.Add(KIND_KEY, KIND_NEW_LINE);
|
||||||
|
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [RULE];
|
||||||
|
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Razor markup ends up in generated code, and so does the code block of a .razor file. We analyze generated
|
||||||
|
// code as well, and the Razor compiler maps the diagnostic back to the line of the .razor file:
|
||||||
|
//
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
|
||||||
|
context.RegisterOperationAction(AnalyzePropertyReference, OperationKind.PropertyReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeInvocation(OperationAnalysisContext context)
|
||||||
|
{
|
||||||
|
var invocation = (IInvocationOperation)context.Operation;
|
||||||
|
if (invocation.TargetMethod.Name is not "AppendLine" || invocation.TargetMethod.ContainingType.ToDisplayString() is not "System.Text.StringBuilder")
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(RULE, invocation.Syntax.GetLocation(), APPEND_LINE_PROPERTIES, "StringBuilder.AppendLine", "AppendLineLF of SharedTools.Text"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzePropertyReference(OperationAnalysisContext context)
|
||||||
|
{
|
||||||
|
var reference = (IPropertyReferenceOperation)context.Operation;
|
||||||
|
if (reference.Property.Name is not "NewLine" || reference.Property.ContainingType.ToDisplayString() is not "System.Environment")
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(RULE, reference.Syntax.GetLocation(), NEW_LINE_PROPERTIES, "Environment.NewLine", "'\\n'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Composition;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CodeActions;
|
||||||
|
using Microsoft.CodeAnalysis.CodeFixes;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
using SourceCodeRules.UsageAnalyzers;
|
||||||
|
|
||||||
|
namespace SourceCodeRules.UsageCodeFixes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes LF instead of the line break of the system: AppendLineLF instead of StringBuilder.AppendLine, and "\n"
|
||||||
|
/// instead of Environment.NewLine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The app imports SharedTools.Text everywhere. Elsewhere, such as in SharedTools or in the tests, the fix adds the
|
||||||
|
/// using directive itself when AppendLineLF would not be found without it.
|
||||||
|
/// </remarks>
|
||||||
|
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SystemLineBreakCodeFixProvider)), Shared]
|
||||||
|
public sealed class SystemLineBreakCodeFixProvider : CodeFixProvider
|
||||||
|
{
|
||||||
|
private const string TITLE_APPEND_LINE = "Use AppendLineLF";
|
||||||
|
|
||||||
|
private const string TITLE_NEW_LINE = "Use a string with LF";
|
||||||
|
|
||||||
|
private const string NAMESPACE_OF_APPEND_LINE_LF = "SharedTools.Text";
|
||||||
|
|
||||||
|
public override ImmutableArray<string> FixableDiagnosticIds => [Identifier.SYSTEM_LINE_BREAK_ANALYZER];
|
||||||
|
|
||||||
|
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
|
||||||
|
|
||||||
|
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||||
|
{
|
||||||
|
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
||||||
|
if (root is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var diagnostic = context.Diagnostics.First();
|
||||||
|
if (!diagnostic.Properties.TryGetValue(SystemLineBreakAnalyzer.KIND_KEY, out var kind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
|
||||||
|
if (kind is SystemLineBreakAnalyzer.KIND_APPEND_LINE && NameOfCall(node.FirstAncestorOrSelf<InvocationExpressionSyntax>()) is { } name)
|
||||||
|
context.RegisterCodeFix(CodeAction.Create(TITLE_APPEND_LINE, token => UseAppendLineLFAsync(context.Document, name, token), TITLE_APPEND_LINE), diagnostic);
|
||||||
|
else if (kind is SystemLineBreakAnalyzer.KIND_NEW_LINE && node is ExpressionSyntax newLine)
|
||||||
|
context.RegisterCodeFix(CodeAction.Create(TITLE_NEW_LINE, token => UseLineFeedAsync(context.Document, newLine, token), TITLE_NEW_LINE), diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the method a call invokes, also behind a ?. of a conditional access.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="call">The call, or null.</param>
|
||||||
|
/// <returns>The name, or null when the call names no member.</returns>
|
||||||
|
private static SimpleNameSyntax? NameOfCall(InvocationExpressionSyntax? call) => call?.Expression switch
|
||||||
|
{
|
||||||
|
MemberAccessExpressionSyntax access => access.Name,
|
||||||
|
MemberBindingExpressionSyntax binding => binding.Name,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
private static async Task<Document> UseAppendLineLFAsync(Document document, SimpleNameSyntax name, CancellationToken token)
|
||||||
|
{
|
||||||
|
var root = await document.GetSyntaxRootAsync(token).ConfigureAwait(false);
|
||||||
|
if (root is null)
|
||||||
|
return document;
|
||||||
|
|
||||||
|
var annotation = new SyntaxAnnotation();
|
||||||
|
var renamed = document.WithSyntaxRoot(root.ReplaceNode(name, SyntaxFactory.IdentifierName("AppendLineLF").WithTriviaFrom(name).WithAdditionalAnnotations(annotation)));
|
||||||
|
var renamedRoot = await renamed.GetSyntaxRootAsync(token).ConfigureAwait(false);
|
||||||
|
var model = await renamed.GetSemanticModelAsync(token).ConfigureAwait(false);
|
||||||
|
if (renamedRoot is not CompilationUnitSyntax unit || model is null)
|
||||||
|
return renamed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Without the namespace of AppendLineLF in scope, the call binds to nothing, and only then is the using
|
||||||
|
// directive missing. It ends with the line break the file already uses:
|
||||||
|
//
|
||||||
|
var call = unit.GetAnnotatedNodes(annotation).First().FirstAncestorOrSelf<InvocationExpressionSyntax>();
|
||||||
|
if (call is null || model.GetSymbolInfo(call, token).Symbol is not null)
|
||||||
|
return renamed;
|
||||||
|
|
||||||
|
var directive = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(NAMESPACE_OF_APPEND_LINE_LF).WithLeadingTrivia(SyntaxFactory.Space)).WithTrailingTrivia(SyntaxFactory.EndOfLine(LineBreakOf(unit)));
|
||||||
|
return renamed.WithSyntaxRoot(unit.AddUsings(directive));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Document> UseLineFeedAsync(Document document, ExpressionSyntax newLine, CancellationToken token)
|
||||||
|
{
|
||||||
|
var root = await document.GetSyntaxRootAsync(token).ConfigureAwait(false);
|
||||||
|
if (root is null)
|
||||||
|
return document;
|
||||||
|
|
||||||
|
var lineFeed = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("\n")).WithTriviaFrom(newLine);
|
||||||
|
return document.WithSyntaxRoot(root.ReplaceNode(newLine, lineFeed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the line break a file uses, so that an added line does not bring a second kind into it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="root">The root of the file.</param>
|
||||||
|
/// <returns>The first line break of the file, or LF when it has none.</returns>
|
||||||
|
private static string LineBreakOf(SyntaxNode root)
|
||||||
|
{
|
||||||
|
var lineBreak = root.DescendantTrivia().FirstOrDefault(trivia => trivia.IsKind(SyntaxKind.EndOfLineTrivia));
|
||||||
|
return lineBreak.IsKind(SyntaxKind.EndOfLineTrivia) ? lineBreak.ToString() : "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using AIStudio.Tests.Models.Generation;
|
||||||
|
|
||||||
|
using SourceCodeRules.UsageAnalyzers;
|
||||||
|
using SourceCodeRules.UsageCodeFixes;
|
||||||
|
|
||||||
|
namespace AIStudio.Tests.Analyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks that MWAIS0018 refuses the line break of the system and that its code fix writes LF instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// StringBuilder.AppendLine and Environment.NewLine write CR LF on Windows. The pipeline runs the tests on Linux
|
||||||
|
/// only, where both write LF, so a text which differs on Windows shows up nowhere but on the computer of a developer.
|
||||||
|
/// </remarks>
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class SystemLineBreakAnalyzerTests
|
||||||
|
{
|
||||||
|
private const string USINGS = """
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
""";
|
||||||
|
|
||||||
|
private const string USINGS_WITH_APPEND_LINE_LF = """
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using SharedTools.Text;
|
||||||
|
""";
|
||||||
|
|
||||||
|
[TestCase("return new StringBuilder().AppendLine().ToString();", "StringBuilder.AppendLine", TestName = "AppendLine without a text")]
|
||||||
|
[TestCase("""return new StringBuilder().AppendLine("Text").ToString();""", "StringBuilder.AppendLine", TestName = "AppendLine with a text")]
|
||||||
|
[TestCase("""return new StringBuilder().AppendLine($"Number {number}").ToString();""", "StringBuilder.AppendLine", TestName = "AppendLine with an interpolated text")]
|
||||||
|
[TestCase("""return $"First{Environment.NewLine}Second";""", "Environment.NewLine", TestName = "Environment.NewLine in an interpolated text")]
|
||||||
|
[TestCase("return string.Join(Environment.NewLine, items);", "Environment.NewLine", TestName = "Environment.NewLine as a separator")]
|
||||||
|
[TestCase("""return "First" + System.Environment.NewLine;""", "Environment.NewLine", TestName = "Environment.NewLine with its namespace")]
|
||||||
|
public async Task TheLineBreakOfTheSystemIsRefused(string statement, string name)
|
||||||
|
{
|
||||||
|
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(USINGS, statement), new SystemLineBreakAnalyzer());
|
||||||
|
|
||||||
|
using (Assert.EnterMultipleScope())
|
||||||
|
{
|
||||||
|
Assert.That(reported, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0018"));
|
||||||
|
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain($"'{name}'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("""return new StringBuilder().AppendLineLF("Text").ToString();""", TestName = "AppendLineLF")]
|
||||||
|
[TestCase("""return new StringBuilder().Append("Text").Append('\n').ToString();""", TestName = "An LF of its own")]
|
||||||
|
[TestCase("""return new Lines().AppendLine("Text");""", TestName = "A method AppendLine of another type")]
|
||||||
|
[TestCase("return Console.Out.NewLine;", TestName = "The line break of a writer")]
|
||||||
|
public async Task AnythingElseIsLeftAlone(string statement)
|
||||||
|
{
|
||||||
|
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(USINGS_WITH_APPEND_LINE_LF, statement), new SystemLineBreakAnalyzer());
|
||||||
|
|
||||||
|
Assert.That(reported, Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TheCodeFixUsesAppendLineLFWhereItIsInScope()
|
||||||
|
{
|
||||||
|
var source = ClassWith(USINGS_WITH_APPEND_LINE_LF, """return new StringBuilder().AppendLine("Text").ToString();""");
|
||||||
|
var expected = ClassWith(USINGS_WITH_APPEND_LINE_LF, """return new StringBuilder().AppendLineLF("Text").ToString();""");
|
||||||
|
|
||||||
|
var fixedSource = await CompilationHarness.FixAsync(source, new SystemLineBreakAnalyzer(), new SystemLineBreakCodeFixProvider());
|
||||||
|
|
||||||
|
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")), "The using directive is there already, so the fix renames the call and adds nothing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TheCodeFixAddsTheUsingDirectiveWhereItIsMissing()
|
||||||
|
{
|
||||||
|
var source = ClassWith(USINGS, """return new StringBuilder().AppendLine($"Number {number}").ToString();""");
|
||||||
|
var expected = ClassWith(USINGS_WITH_APPEND_LINE_LF, """return new StringBuilder().AppendLineLF($"Number {number}").ToString();""");
|
||||||
|
|
||||||
|
var fixedSource = await CompilationHarness.FixAsync(source, new SystemLineBreakAnalyzer(), new SystemLineBreakCodeFixProvider());
|
||||||
|
|
||||||
|
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TheCodeFixWritesAStringWithLfForEnvironmentNewLine()
|
||||||
|
{
|
||||||
|
var source = ClassWith(USINGS, "return string.Join(Environment.NewLine, items);");
|
||||||
|
var expected = ClassWith(USINGS, """return string.Join("\n", items);""");
|
||||||
|
|
||||||
|
var fixedSource = await CompilationHarness.FixAsync(source, new SystemLineBreakAnalyzer(), new SystemLineBreakCodeFixProvider());
|
||||||
|
|
||||||
|
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ClassWith(string usings, string statement) =>
|
||||||
|
$$"""
|
||||||
|
{{usings}}
|
||||||
|
|
||||||
|
namespace Sample;
|
||||||
|
|
||||||
|
public sealed class Lines
|
||||||
|
{
|
||||||
|
public string AppendLine(string text) => text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Texts
|
||||||
|
{
|
||||||
|
public static string Build(string[] items, int number)
|
||||||
|
{
|
||||||
|
{{statement}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
@@ -141,7 +141,7 @@ public sealed class ChatThreadEditTests
|
|||||||
Directory.CreateDirectory(transcriptDirectory);
|
Directory.CreateDirectory(transcriptDirectory);
|
||||||
|
|
||||||
var path = Path.Combine(transcriptDirectory, $"{stem}-transcript-0001.md");
|
var path = Path.Combine(transcriptDirectory, $"{stem}-transcript-0001.md");
|
||||||
File.WriteAllText(path, $"# Transcription: {stem}.mp4{Environment.NewLine}{Environment.NewLine}Hello and welcome.");
|
File.WriteAllText(path, $"# Transcription: {stem}.mp4\n\nHello and welcome.");
|
||||||
return new ManagedTranscriptAttachment(Path.GetFileName(path), path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
return new ManagedTranscriptAttachment(Path.GetFileName(path), path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ public sealed class ContentExtensionsTests
|
|||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(exported, Does.StartWith(file.Content), "The document stays as the model wrote it.");
|
Assert.That(exported, Does.StartWith(file.Content), "The document stays as the model wrote it.");
|
||||||
Assert.That(appended.Split(Environment.NewLine), Has.All.StartWith("%"), "A line LaTeX would read could stop the whole run.");
|
Assert.That(appended.Split('\n'), Has.All.StartWith("%"), "A line LaTeX would read could stop the whole run.");
|
||||||
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,5 +347,5 @@ public sealed class ContentExtensionsTests
|
|||||||
.Select(block => block is HeadingBlock heading ? $"h{heading.Level}" : block.GetType().Name)
|
.Select(block => block is HeadingBlock heading ? $"h{heading.Level}" : block.GetType().Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ public sealed class MarkdownRenderPlannerTests
|
|||||||
{
|
{
|
||||||
var parts = Parts("<html><body><p>Hi</p></body></html>");
|
var parts = Parts("<html><body><p>Hi</p></body></html>");
|
||||||
|
|
||||||
Assert.That(parts, Is.EqualTo(new[] { $"MARKDOWN: ```html{Environment.NewLine}<html><body><p>Hi</p></body></html>{Environment.NewLine}```" }));
|
Assert.That(parts, Is.EqualTo(new[] { "MARKDOWN: ```html\n<html><body><p>Hi</p></body></html>\n```" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -92,5 +92,5 @@ public sealed class ExportableMarkdownTests
|
|||||||
|
|
||||||
private static MarkdownDocument Render(string answer) => Markdig.Markdown.Parse(answer, Markdown.CHAT_MARKDOWN_PIPELINE);
|
private static MarkdownDocument Render(string answer) => Markdig.Markdown.Parse(answer, Markdown.CHAT_MARKDOWN_PIPELINE);
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ public sealed class CapabilityCharacterizationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(CapabilitySnapshot.ACTUAL_FILE_PATH, current);
|
File.WriteAllText(CapabilitySnapshot.ACTUAL_FILE_PATH, current);
|
||||||
Assert.Fail($"The capabilities of {DescribeDifference(recorded, current)}{Environment.NewLine}{Environment.NewLine}The full result was written to {CapabilitySnapshot.ACTUAL_FILE_PATH}.");
|
Assert.Fail($"The capabilities of {DescribeDifference(recorded, current)}\n\nThe full result was written to {CapabilitySnapshot.ACTUAL_FILE_PATH}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -76,19 +76,19 @@ public sealed class CapabilityCharacterizationTests
|
|||||||
var added = currentLines.Keys.Except(recordedLines.Keys).ToList();
|
var added = currentLines.Keys.Except(recordedLines.Keys).ToList();
|
||||||
var removed = recordedLines.Keys.Except(currentLines.Keys).ToList();
|
var removed = recordedLines.Keys.Except(currentLines.Keys).ToList();
|
||||||
|
|
||||||
var message = new StringBuilder($"{changed.Count} model(s) changed, {added.Count} came into the corpus, {removed.Count} left it:").Append(Environment.NewLine);
|
var message = new StringBuilder($"{changed.Count} model(s) changed, {added.Count} came into the corpus, {removed.Count} left it:").Append('\n');
|
||||||
foreach (var model in changed.Take(LINES_SHOWN))
|
foreach (var model in changed.Take(LINES_SHOWN))
|
||||||
{
|
{
|
||||||
message.Append(Environment.NewLine).Append(" ").Append(model);
|
message.Append('\n').Append(" ").Append(model);
|
||||||
message.Append(Environment.NewLine).Append(" was: ").Append(recordedLines[model]);
|
message.Append('\n').Append(" was: ").Append(recordedLines[model]);
|
||||||
message.Append(Environment.NewLine).Append(" now: ").Append(currentLines[model]);
|
message.Append('\n').Append(" now: ").Append(currentLines[model]);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var model in added.Take(LINES_SHOWN))
|
foreach (var model in added.Take(LINES_SHOWN))
|
||||||
message.Append(Environment.NewLine).Append(" + ").Append(model).Append(": ").Append(currentLines[model]);
|
message.Append('\n').Append(" + ").Append(model).Append(": ").Append(currentLines[model]);
|
||||||
|
|
||||||
foreach (var model in removed.Take(LINES_SHOWN))
|
foreach (var model in removed.Take(LINES_SHOWN))
|
||||||
message.Append(Environment.NewLine).Append(" - ").Append(model).Append(": ").Append(recordedLines[model]);
|
message.Append('\n').Append(" - ").Append(model).Append(": ").Append(recordedLines[model]);
|
||||||
|
|
||||||
return message.ToString();
|
return message.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public static class CompilationHarness
|
|||||||
.Where(diagnostic => diagnostic.Severity is DiagnosticSeverity.Error)
|
.Where(diagnostic => diagnostic.Severity is DiagnosticSeverity.Error)
|
||||||
.Select(diagnostic => diagnostic.ToString());
|
.Select(diagnostic => diagnostic.ToString());
|
||||||
|
|
||||||
return string.Join(Environment.NewLine, errors);
|
return string.Join('\n', errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using System.Text;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
|
using SharedTools.Text;
|
||||||
|
|
||||||
namespace AIStudio.Tests.Serialization;
|
namespace AIStudio.Tests.Serialization;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -30,20 +32,20 @@ public static class JsonContractDescription
|
|||||||
: $"converter {ConverterName(typeInfo.Converter)}, created {(typeInfo.CreateObject is null ? "otherwise" : "without arguments")}";
|
: $"converter {ConverterName(typeInfo.Converter)}, created {(typeInfo.CreateObject is null ? "otherwise" : "without arguments")}";
|
||||||
|
|
||||||
var text = new StringBuilder();
|
var text = new StringBuilder();
|
||||||
text.AppendLine($"{TypeName(typeInfo.Type)}: {typeInfo.Kind}, {creation}");
|
text.AppendLineLF($"{TypeName(typeInfo.Type)}: {typeInfo.Kind}, {creation}");
|
||||||
text.AppendLine($" handling: numbers {typeInfo.NumberHandling}, unmapped members {typeInfo.UnmappedMemberHandling}, object creation {typeInfo.PreferredPropertyObjectCreationHandling}");
|
text.AppendLineLF($" handling: numbers {typeInfo.NumberHandling}, unmapped members {typeInfo.UnmappedMemberHandling}, object creation {typeInfo.PreferredPropertyObjectCreationHandling}");
|
||||||
text.AppendLine($" callbacks: serializing {typeInfo.OnSerializing is not null}, serialized {typeInfo.OnSerialized is not null}, deserializing {typeInfo.OnDeserializing is not null}, deserialized {typeInfo.OnDeserialized is not null}");
|
text.AppendLineLF($" callbacks: serializing {typeInfo.OnSerializing is not null}, serialized {typeInfo.OnSerialized is not null}, deserializing {typeInfo.OnDeserializing is not null}, deserialized {typeInfo.OnDeserialized is not null}");
|
||||||
if (typeInfo.KeyType is { } keyType)
|
if (typeInfo.KeyType is { } keyType)
|
||||||
text.AppendLine($" key: {TypeName(keyType)}");
|
text.AppendLineLF($" key: {TypeName(keyType)}");
|
||||||
|
|
||||||
if (typeInfo.ElementType is { } elementType)
|
if (typeInfo.ElementType is { } elementType)
|
||||||
text.AppendLine($" element: {TypeName(elementType)}");
|
text.AppendLineLF($" element: {TypeName(elementType)}");
|
||||||
|
|
||||||
if (typeInfo.PolymorphismOptions is { } polymorphism)
|
if (typeInfo.PolymorphismOptions is { } polymorphism)
|
||||||
{
|
{
|
||||||
text.AppendLine($" polymorphism: discriminator {polymorphism.TypeDiscriminatorPropertyName}, unrecognized ignored {polymorphism.IgnoreUnrecognizedTypeDiscriminators}, unknown types {polymorphism.UnknownDerivedTypeHandling}");
|
text.AppendLineLF($" polymorphism: discriminator {polymorphism.TypeDiscriminatorPropertyName}, unrecognized ignored {polymorphism.IgnoreUnrecognizedTypeDiscriminators}, unknown types {polymorphism.UnknownDerivedTypeHandling}");
|
||||||
foreach (var derivedType in polymorphism.DerivedTypes)
|
foreach (var derivedType in polymorphism.DerivedTypes)
|
||||||
text.AppendLine($" derived: {TypeName(derivedType.DerivedType)} as {derivedType.TypeDiscriminator}");
|
text.AppendLineLF($" derived: {TypeName(derivedType.DerivedType)} as {derivedType.TypeDiscriminator}");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var property in typeInfo.Properties)
|
foreach (var property in typeInfo.Properties)
|
||||||
@@ -53,7 +55,7 @@ public static class JsonContractDescription
|
|||||||
//
|
//
|
||||||
if (IsIgnored(property))
|
if (IsIgnored(property))
|
||||||
{
|
{
|
||||||
text.AppendLine($" property {property.Name}: ignored");
|
text.AppendLineLF($" property {property.Name}: ignored");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ public static class JsonContractDescription
|
|||||||
?$"set {property.Set is not null || property.AssociatedParameter is not null}"
|
?$"set {property.Set is not null || property.AssociatedParameter is not null}"
|
||||||
: $"set {property.Set is not null}, constructor parameter {property.AssociatedParameter?.Name ?? "none"}";
|
: $"set {property.Set is not null}, constructor parameter {property.AssociatedParameter?.Name ?? "none"}";
|
||||||
|
|
||||||
text.AppendLine($" property {property.Name}: {TypeName(property.PropertyType)}, order {property.Order}, get {property.Get is not null}, {setting}, required {property.IsRequired}, extension data {property.IsExtensionData}, conditional {property.ShouldSerialize is not null}, converter {ConverterName(property.CustomConverter)}, nullable get {property.IsGetNullable}, nullable set {property.IsSetNullable}, numbers {property.NumberHandling}, object creation {property.ObjectCreationHandling}");
|
text.AppendLineLF($" property {property.Name}: {TypeName(property.PropertyType)}, order {property.Order}, get {property.Get is not null}, {setting}, required {property.IsRequired}, extension data {property.IsExtensionData}, conditional {property.ShouldSerialize is not null}, converter {ConverterName(property.CustomConverter)}, nullable get {property.IsGetNullable}, nullable set {property.IsSetNullable}, numbers {property.NumberHandling}, object creation {property.ObjectCreationHandling}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.ToString();
|
return text.ToString();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public sealed class UserProvidedAPIKeyOptionsTests
|
|||||||
|
|
||||||
var descriptor = PluginConfigurationObjectDescriptors.Get(type);
|
var descriptor = PluginConfigurationObjectDescriptors.Get(type);
|
||||||
var section = descriptor.LuaTableName;
|
var section = descriptor.LuaTableName;
|
||||||
var optionLines = string.Join(Environment.NewLine, options.Select(option => $"CONFIG[\"{section}\"][1]{option}"));
|
var optionLines = string.Join('\n', options.Select(option => $"CONFIG[\"{section}\"][1]{option}"));
|
||||||
|
|
||||||
var state = LuaState.Create();
|
var state = LuaState.Create();
|
||||||
state.OpenBasicLibrary();
|
state.OpenBasicLibrary();
|
||||||
|
|||||||
+14
-5
@@ -37,15 +37,24 @@
|
|||||||
<ProjectReference Include="..\MindWork AI Studio\MindWork AI Studio.csproj" />
|
<ProjectReference Include="..\MindWork AI Studio\MindWork AI Studio.csproj" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Referenced as plain libraries, not as analyzers: the tests run the generator and the
|
Referenced as libraries: the tests run the generator and the analyzers themselves, against
|
||||||
analyzer themselves, against a snippet they compile in memory. Building them into this
|
a snippet they compile in memory. SourceCodeRules is an analyzer of this project as well,
|
||||||
project as analyzers would have them judge the test code instead, which is not what is
|
but only for MWAIS0018, see below: a test which expects the line break of the system fails
|
||||||
being checked here.
|
on Windows only, where no pipeline runs the tests. The other rules are written for the code
|
||||||
|
of the app and would judge test code which has its reasons, such as new Random() with a seed.
|
||||||
-->
|
-->
|
||||||
<ProjectReference Include="..\SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj" />
|
<ProjectReference Include="..\SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj" OutputItemType="Analyzer" />
|
||||||
<ProjectReference Include="..\SourceGeneratedMappings\SourceGeneratedMappings.csproj" />
|
<ProjectReference Include="..\SourceGeneratedMappings\SourceGeneratedMappings.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Every rule of SourceCodeRules except MWAIS0018, the line break of the system. A new rule judges
|
||||||
|
the tests, too, until it is listed here.
|
||||||
|
-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);MWAIS0001;MWAIS0002;MWAIS0003;MWAIS0004;MWAIS0005;MWAIS0006;MWAIS0007;MWAIS0008;MWAIS0009;MWAIS0010;MWAIS0011;MWAIS0012;MWAIS0013;MWAIS0014;MWAIS0015;MWAIS0016;MWAIS0017</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Compiled here as well, so that the tests check the same options of the app which the trim smoke test finds.
|
Compiled here as well, so that the tests check the same options of the app which the trim smoke test finds.
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -78,5 +78,5 @@ public sealed class AIGeneratedContentDisclosureServiceTests
|
|||||||
|
|
||||||
private static SettingsManager CreateSettingsManager() => new(NullLogger<SettingsManager>.Instance, null!);
|
private static SettingsManager CreateSettingsManager() => new(NullLogger<SettingsManager>.Instance, null!);
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,8 @@ public sealed class ClassicRagPassagesTests
|
|||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(augmentation.UsedContexts.Select(context => context.Context), Is.EqualTo(new IRetrievalContext[] { handbook, manual }), "Without the check, the AI receives every context, in the order it was retrieved.");
|
Assert.That(augmentation.UsedContexts.Select(context => context.Context), Is.EqualTo(new IRetrievalContext[] { handbook, manual }), "Without the check, the AI receives every context, in the order it was retrieved.");
|
||||||
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain($"# Retrieval context 1 of 2{Environment.NewLine}"));
|
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain("# Retrieval context 1 of 2\n"));
|
||||||
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain($"# Retrieval context 2 of 2{Environment.NewLine}"));
|
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain("# Retrieval context 2 of 2\n"));
|
||||||
Assert.That(this.runtime.FilteredBatches, Has.Count.EqualTo(1), "All contexts are filtered together, in one request.");
|
Assert.That(this.runtime.FilteredBatches, Has.Count.EqualTo(1), "All contexts are filtered together, in one request.");
|
||||||
Assert.That(this.runtime.FilteredTexts, Has.Count.EqualTo(4), "Each description and each matched text is filtered once, and nothing a second time on its own.");
|
Assert.That(this.runtime.FilteredTexts, Has.Count.EqualTo(4), "Each description and each matched text is filtered once, and nothing a second time on its own.");
|
||||||
}
|
}
|
||||||
@@ -124,9 +124,9 @@ public sealed class ClassicRagPassagesTests
|
|||||||
{
|
{
|
||||||
Assert.That(augmentation.UsedContexts.Select(context => context.Context), Is.EqualTo(new IRetrievalContext[] { handbookIntroduction, manual }), "The best passage of each data source fits; the second best of the handbook does not.");
|
Assert.That(augmentation.UsedContexts.Select(context => context.Context), Is.EqualTo(new IRetrievalContext[] { handbookIntroduction, manual }), "The best passage of each data source fits; the second best of the handbook does not.");
|
||||||
Assert.That(augmentation.PassageCounts, Is.EqualTo(new[] { 1, 1 }));
|
Assert.That(augmentation.PassageCounts, Is.EqualTo(new[] { 1, 1 }));
|
||||||
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain($"# Retrieval context 2 of 2{Environment.NewLine}"));
|
Assert.That(augmentation.ChatThread.AugmentedData, Does.Contain("# Retrieval context 2 of 2\n"));
|
||||||
Assert.That(augmentation.ChatThread.AugmentedData, Does.Not.Contain("The results of the handbook."));
|
Assert.That(augmentation.ChatThread.AugmentedData, Does.Not.Contain("The results of the handbook."));
|
||||||
Assert.That(augmentation.ChatThread.AugmentedData, Does.EndWith($"1 did not fit into this request and was left out. An answer based on the data sources may therefore be incomplete.{Environment.NewLine}"), "The model learns that it did not read everything the search found.");
|
Assert.That(augmentation.ChatThread.AugmentedData, Does.EndWith("1 did not fit into this request and was left out. An answer based on the data sources may therefore be incomplete.\n"), "The model learns that it did not read everything the search found.");
|
||||||
Assert.That(warnings.Messages, Has.Count.EqualTo(1));
|
Assert.That(warnings.Messages, Has.Count.EqualTo(1));
|
||||||
Assert.That(warnings.Messages.SingleOrDefault(), Does.Contain("had to do without 1 of them"), "The user learns about it with the answer.");
|
Assert.That(warnings.Messages.SingleOrDefault(), Does.Contain("had to do without 1 of them"), "The user learns about it with the answer.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public sealed class FileExportFormatTests
|
|||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(comment.Split(Environment.NewLine), Is.EqualTo(new[] { "% # Sources", "%", "% - [1] A title with 100 % and a_b" }), "LaTeX has no end of a comment, only the end of a line.");
|
Assert.That(comment.Split('\n'), Is.EqualTo(new[] { "% # Sources", "%", "% - [1] A title with 100 % and a_b" }), "LaTeX has no end of a comment, only the end of a line.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,5 +177,5 @@ public sealed class FileExportFormatTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
@@ -93,5 +93,5 @@ public sealed class MarkdownTests
|
|||||||
Assert.That(Markdown.AppendBlock(string.Empty, "**Notice**"), Is.EqualTo("**Notice**"), "With nothing above it, a blank line would only start the text with an empty line.");
|
Assert.That(Markdown.AppendBlock(string.Empty, "**Notice**"), Is.EqualTo("**Notice**"), "With nothing above it, a blank line would only start the text with an empty line.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
}
|
}
|
||||||
@@ -268,7 +268,7 @@ public sealed class PlainFileExportTests
|
|||||||
"```");
|
"```");
|
||||||
|
|
||||||
var files = PlainFileExport.ExtractFiles(MarkdownRenderPlanner.Build(answer), ',');
|
var files = PlainFileExport.ExtractFiles(MarkdownRenderPlanner.Build(answer), ',');
|
||||||
var partStart = answer.LastIndexOf("$$", StringComparison.Ordinal) + "$$".Length + Environment.NewLine.Length;
|
var partStart = answer.LastIndexOf("$$", StringComparison.Ordinal) + "$$\n".Length;
|
||||||
var part = Markdig.Markdown.Parse(answer[partStart..], Markdown.CHAT_MARKDOWN_PIPELINE);
|
var part = Markdig.Markdown.Parse(answer[partStart..], Markdown.CHAT_MARKDOWN_PIPELINE);
|
||||||
var blockOfThePart = part.Descendants<FencedCodeBlock>().Single();
|
var blockOfThePart = part.Descendants<FencedCodeBlock>().Single();
|
||||||
|
|
||||||
@@ -280,7 +280,19 @@ public sealed class PlainFileExportTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Lines(params string[] lines) => string.Join(Environment.NewLine, lines);
|
[Test]
|
||||||
|
public void EveryRowOfATableEndsWithCrLfAsCsvAsksFor()
|
||||||
|
{
|
||||||
|
var files = PlainFileExport.ExtractFiles(MarkdownRenderPlanner.Build(Lines("| Quarter | Revenue |", "|---|---|", "| Q1 | 100 |")), ',');
|
||||||
|
|
||||||
|
using (Assert.EnterMultipleScope())
|
||||||
|
{
|
||||||
|
Assert.That(files, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(files.FirstOrDefault()?.Content, Is.EqualTo("Quarter,Revenue\r\nQ1,100\r\n"), "RFC 4180 ends every row with CR LF, on every system.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Lines(params string[] lines) => string.Join('\n', lines);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads a file from the fixtures next to this test.
|
/// Reads a file from the fixtures next to this test.
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using AIStudio.Tools.Services;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
using SharedTools.Text;
|
||||||
|
|
||||||
namespace AIStudio.Tests.Tools.PluginSystem;
|
namespace AIStudio.Tests.Tools.PluginSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -204,7 +206,7 @@ public sealed class ManagedSettingsCoverageTests
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
foreach (var setting in ManagedConfiguration.RegisteredSettings)
|
foreach (var setting in ManagedConfiguration.RegisteredSettings)
|
||||||
code.AppendLine($"""CONFIG["SETTINGS"]["{setting.SettingName}"] = {ToLuaValue(setting)}""");
|
code.AppendLineLF($"""CONFIG["SETTINGS"]["{setting.SettingName}"] = {ToLuaValue(setting)}""");
|
||||||
|
|
||||||
var plugin = await PluginFactory.Load(null, code.ToString());
|
var plugin = await PluginFactory.Load(null, code.ToString());
|
||||||
if (plugin is not PluginConfiguration { IsValid: true } configurationPlugin)
|
if (plugin is not PluginConfiguration { IsValid: true } configurationPlugin)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public sealed class RelativeDataPathsMigrationTests
|
|||||||
Directory.CreateDirectory(transcriptDirectory);
|
Directory.CreateDirectory(transcriptDirectory);
|
||||||
|
|
||||||
var transcriptPath = Path.Combine(transcriptDirectory, "meeting-transcript-0001.md");
|
var transcriptPath = Path.Combine(transcriptDirectory, "meeting-transcript-0001.md");
|
||||||
File.WriteAllText(transcriptPath, $"# Transcription: meeting.mp4{Environment.NewLine}{Environment.NewLine}Hello and welcome.");
|
File.WriteAllText(transcriptPath, $"# Transcription: meeting.mp4\n\nHello and welcome.");
|
||||||
var transcript = new ManagedTranscriptAttachment("meeting-transcript-0001.md", transcriptPath, new FileInfo(transcriptPath).Length, "meeting.mp4", false);
|
var transcript = new ManagedTranscriptAttachment("meeting-transcript-0001.md", transcriptPath, new FileInfo(transcriptPath).Length, "meeting.mp4", false);
|
||||||
|
|
||||||
var chat = new ChatThread
|
var chat = new ChatThread
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using AIStudio.Tools.Services;
|
|||||||
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
|
using SharedTools.Text;
|
||||||
|
|
||||||
namespace AIStudio.Tests.Tools;
|
namespace AIStudio.Tests.Tools;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,7 +89,7 @@ public sealed class RetrievalContextPreparationTests
|
|||||||
"",
|
"",
|
||||||
];
|
];
|
||||||
|
|
||||||
Assert.That(prepared.Single().ToMarkdown(2, 5), Is.EqualTo(string.Join(Environment.NewLine, expectedLines) + Environment.NewLine), "The model reads the passages in a layout it was prompted for, so preparing them must not change it.");
|
Assert.That(prepared.Single().ToMarkdown(2, 5), Is.EqualTo(string.Join('\n', expectedLines) + "\n"), "The model reads the passages in a layout it was prompted for, so preparing them must not change it.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -97,9 +99,9 @@ public sealed class RetrievalContextPreparationTests
|
|||||||
|
|
||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prepared.ToMarkdown(3), Does.StartWith($"# Retrieval context 3{Environment.NewLine}Data source name:"), "Semantic Search numbers its passages without knowing how many fit its budget.");
|
Assert.That(prepared.ToMarkdown(3), Does.StartWith("# Retrieval context 3\nData source name:"), "Semantic Search numbers its passages without knowing how many fit its budget.");
|
||||||
Assert.That(prepared.ToMarkdown(), Does.StartWith($"# Retrieval context{Environment.NewLine}Data source name:"), "A context standing alone has no number.");
|
Assert.That(prepared.ToMarkdown(), Does.StartWith("# Retrieval context\nData source name:"), "A context standing alone has no number.");
|
||||||
Assert.That(prepared.ToMarkdown(2, 5), Is.EqualTo($"# Retrieval context 2 of 5{Environment.NewLine}{prepared.Body}{Environment.NewLine}"), "Below its header, a context is its body.");
|
Assert.That(prepared.ToMarkdown(2, 5), Is.EqualTo($"# Retrieval context 2 of 5\n{prepared.Body}\n"), "Below its header, a context is its body.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,10 +121,10 @@ public sealed class RetrievalContextPreparationTests
|
|||||||
var prepared = (await this.PrepareAsync(image)).Single();
|
var prepared = (await this.PrepareAsync(image)).Single();
|
||||||
|
|
||||||
var expected = new StringBuilder();
|
var expected = new StringBuilder();
|
||||||
expected.AppendLine("# Retrieval context");
|
expected.AppendLineLF("# Retrieval context");
|
||||||
RetrievalContextExtensions.AppendContextDescription(expected, image);
|
RetrievalContextExtensions.AppendContextDescription(expected, image);
|
||||||
RetrievalContextExtensions.AppendImageMatch(expected);
|
RetrievalContextExtensions.AppendImageMatch(expected);
|
||||||
expected.AppendLine();
|
expected.AppendLineLF();
|
||||||
|
|
||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ public sealed class SourceExtensionsTests
|
|||||||
/// <param name="markdown">The Markdown of the sources.</param>
|
/// <param name="markdown">The Markdown of the sources.</param>
|
||||||
/// <returns>The entries, in the order they stand in.</returns>
|
/// <returns>The entries, in the order they stand in.</returns>
|
||||||
private static IReadOnlyList<string> EntriesOf(string markdown) => markdown
|
private static IReadOnlyList<string> EntriesOf(string markdown) => markdown
|
||||||
.Split(Environment.NewLine, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Where(line => line.StartsWith("- [", StringComparison.Ordinal))
|
.Where(line => line.StartsWith("- [", StringComparison.Ordinal))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using SharedTools;
|
using SharedTools.Text;
|
||||||
|
|
||||||
namespace AIStudio.Tests.Tools;
|
namespace AIStudio.Tests.Tools;
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ public sealed class SearchMailsToolTests : ToolRegistryTestBase
|
|||||||
|
|
||||||
using (Assert.EnterMultipleScope())
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(function.DescriptionForLLM, Does.Contain($"- id={WORK.Id}, name='Work'{Environment.NewLine}"));
|
Assert.That(function.DescriptionForLLM, Does.Contain($"- id={WORK.Id}, name='Work'\n"));
|
||||||
Assert.That(function.DescriptionForLLM, Does.EndWith($"- id={PRIVATE.Id}, name='Private'"));
|
Assert.That(function.DescriptionForLLM, Does.EndWith($"- id={PRIVATE.Id}, name='Private'"));
|
||||||
Assert.That(function.DescriptionForLLM, Does.Not.Contain("per page").And.Not.Contain("last page"), "How many mails a page holds depends on how long they are, so each result tells whether a further page holds more.");
|
Assert.That(function.DescriptionForLLM, Does.Not.Contain("per page").And.Not.Contain("last page"), "How many mails a page holds depends on how long they are, so each result tells whether a further page holds more.");
|
||||||
Assert.That(properties[MailToolArguments.MAILBOX_IDS_ARGUMENT]!["items"]!["enum"]!.AsArray().Select(id => id!.GetValue<string>()), Is.EqualTo(new[] { WORK.Id, PRIVATE.Id }));
|
Assert.That(properties[MailToolArguments.MAILBOX_IDS_ARGUMENT]!["items"]!["enum"]!.AsArray().Select(id => id!.GetValue<string>()), Is.EqualTo(new[] { WORK.Id, PRIVATE.Id }));
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public sealed partial class SemanticSearchToolPagingTests
|
|||||||
{
|
{
|
||||||
var result = await this.SearchAsync([HANDBOOK], 2, Found(Passages(1, 10, LONG)));
|
var result = await this.SearchAsync([HANDBOOK], 2, Found(Passages(1, 10, LONG)));
|
||||||
|
|
||||||
Assert.That(result.TextContent, Does.StartWith($"# Retrieval context 4{Environment.NewLine}"), "The model reads the fourth passage of the search, not the first one a second time.");
|
Assert.That(result.TextContent, Does.StartWith("# Retrieval context 4\n"), "The model reads the fourth passage of the search, not the first one a second time.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -230,7 +230,7 @@ public sealed partial class SemanticSearchToolPagingTests
|
|||||||
Assert.That(shown.Text, Is.EqualTo(passage.MatchedText), "The matched text is what the search found, so it is the last to be shortened.");
|
Assert.That(shown.Text, Is.EqualTo(passage.MatchedText), "The matched text is what the search found, so it is the last to be shortened.");
|
||||||
Assert.That(shown.SurroundingContent[0], Is.EqualTo(passage.SurroundingContent[0]));
|
Assert.That(shown.SurroundingContent[0], Is.EqualTo(passage.SurroundingContent[0]));
|
||||||
Assert.That(shown.SurroundingContent[^1], Does.StartWith("After ").And.Length.LessThan(LENGTH), "The surrounding content is shortened from its end.");
|
Assert.That(shown.SurroundingContent[^1], Does.StartWith("After ").And.Length.LessThan(LENGTH), "The surrounding content is shortened from its end.");
|
||||||
Assert.That(result.TextContent, Does.Contain($"Matched text content:{Environment.NewLine}").And.Contain("Surrounding text content, shortened to fit the size limit:"));
|
Assert.That(result.TextContent, Does.Contain("Matched text content:\n").And.Contain("Surrounding text content, shortened to fit the size limit:"));
|
||||||
Assert.That(result.TextContent.Length, Is.LessThanOrEqualTo(SemanticSearchTool.MAX_RESULT_CHARACTERS));
|
Assert.That(result.TextContent.Length, Is.LessThanOrEqualTo(SemanticSearchTool.MAX_RESULT_CHARACTERS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public sealed class SemanticSearchToolRequestTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void AQueryOfSeveralLinesIsRefused()
|
public void AQueryOfSeveralLinesIsRefused()
|
||||||
{
|
{
|
||||||
var message = Refusal(() => SemanticSearchTool.ReadRequest(Query($"travel expenses{Environment.NewLine}hotels"), OFFERED));
|
var message = Refusal(() => SemanticSearchTool.ReadRequest(Query("travel expenses\nhotels"), OFFERED));
|
||||||
Assert.That(message, Does.Contain("'query'").And.Contain("single line"));
|
Assert.That(message, Does.Contain("'query'").And.Contain("single line"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public sealed class WorkspaceChatCopyTests
|
|||||||
Directory.CreateDirectory(transcriptDirectory);
|
Directory.CreateDirectory(transcriptDirectory);
|
||||||
|
|
||||||
var path = Path.Combine(transcriptDirectory, $"{stem}-transcript-0001.md");
|
var path = Path.Combine(transcriptDirectory, $"{stem}-transcript-0001.md");
|
||||||
File.WriteAllText(path, $"# Transcription: {stem}.mp4{Environment.NewLine}{Environment.NewLine}Hello and welcome.");
|
File.WriteAllText(path, $"# Transcription: {stem}.mp4\n\nHello and welcome.");
|
||||||
return new ManagedTranscriptAttachment(Path.GetFileName(path), path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
return new ManagedTranscriptAttachment(Path.GetFileName(path), path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ public sealed class WorkspaceChatMoveTests
|
|||||||
|
|
||||||
var fileName = $"{stem}-transcript-{number:D4}.md";
|
var fileName = $"{stem}-transcript-{number:D4}.md";
|
||||||
var path = Path.Combine(transcriptDirectory, fileName);
|
var path = Path.Combine(transcriptDirectory, fileName);
|
||||||
File.WriteAllText(path, $"# Transcription: {stem}.mp4{Environment.NewLine}{Environment.NewLine}Hello and welcome.");
|
File.WriteAllText(path, $"# Transcription: {stem}.mp4\n\nHello and welcome.");
|
||||||
return new ManagedTranscriptAttachment(fileName, path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
return new ManagedTranscriptAttachment(fileName, path, new FileInfo(path).Length, $"{stem}.mp4", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user