Improved source links in chats (#827)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Peer Hogeterp 2026-07-04 14:23:20 +02:00 committed by GitHub
parent 796855f0ef
commit dd72fd5f8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 11 deletions

View File

@ -1,13 +1,84 @@
using System.Text;
using System.Text.RegularExpressions;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools;
public static class SourceExtensions
public static partial class SourceExtensions
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SourceExtensions).Namespace, nameof(SourceExtensions));
private static void AppendMarkdownLink(StringBuilder sb, string title, string url)
{
sb.Append('[');
sb.Append(EscapeMarkdownLinkText(title));
sb.Append("](<");
sb.Append(NormalizeLinkDestination(url));
sb.Append(">)");
}
private static string EscapeMarkdownLinkText(string text)
{
return text
.Replace(@"\", @"\\")
.Replace("[", @"\[")
.Replace("]", @"\]")
.Replace("\r", " ")
.Replace("\n", " ");
}
private static string NormalizeLinkDestination(string url)
{
var normalized = url.Trim().Replace("\r", string.Empty).Replace("\n", string.Empty);
normalized = TryUnwrapMarkdownLink(normalized);
if (Uri.TryCreate(normalized, UriKind.Absolute, out var absoluteUri))
return absoluteUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped);
var sb = new StringBuilder(normalized.Length);
foreach (var c in normalized)
{
if (IsSafeUrlCharacter(c))
{
sb.Append(c);
continue;
}
sb.Append(Uri.EscapeDataString(c.ToString()));
}
return sb.ToString();
}
private static string TryUnwrapMarkdownLink(string value)
{
var match = MarkdownLinkWithOptionalSuffix().Match(value);
if (!match.Success)
return value;
var label = match.Groups["label"].Value;
var url = match.Groups["url"].Value;
var suffix = match.Groups["suffix"].Value;
if (string.IsNullOrEmpty(suffix))
return url;
if (Uri.TryCreate(label, UriKind.Absolute, out var labelUri) &&
Uri.TryCreate(url, UriKind.Absolute, out var urlUri) &&
Uri.Compare(labelUri, urlUri, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0)
return url + suffix;
return value;
}
private static bool IsSafeUrlCharacter(char c)
{
if (char.IsAsciiLetterOrDigit(c))
return true;
return c is '-' or '.' or '_' or '~' or ':' or '/' or '?' or '#' or '[' or ']' or '@' or '!' or '$' or '&' or '\'' or '(' or ')' or '*' or '+' or ',' or ';' or '=';
}
/// <summary>
/// Converts a list of sources to a markdown-formatted string.
/// </summary>
@ -36,11 +107,8 @@ public static class SourceExtensions
}
sb.Append($"- [{++sourceNum}] ");
sb.Append('[');
sb.Append(source.Title);
sb.Append("](");
sb.Append(source.URL);
sb.AppendLine(")");
AppendMarkdownLink(sb, source.Title, source.URL);
sb.AppendLine();
break;
}
}
@ -55,11 +123,8 @@ public static class SourceExtensions
foreach (var source in ragSources)
{
sb.Append($"- [{++sourceNum}] ");
sb.Append('[');
sb.Append(source.Title);
sb.Append("](");
sb.Append(source.URL);
sb.AppendLine(")");
AppendMarkdownLink(sb, source.Title, source.URL);
sb.AppendLine();
}
return sb.ToString();
@ -76,4 +141,7 @@ public static class SourceExtensions
if (sources.All(s => s.URL != addedSource.URL && s.Title != addedSource.Title))
sources.Add((Source)addedSource);
}
[GeneratedRegex(@"^\[(?<label>[^\]]+)\]\((?<url>[^)\r\n]+)\)(?<suffix>.*)$")]
private static partial Regex MarkdownLinkWithOptionalSuffix();
}

View File

@ -1,4 +1,5 @@
# v26.6.3, build 243 (2026-06-xx xx:xx UTC)
- Improved source links in chat answers when file or document names contained spaces, umlauts, or other special characters. Source entries now open much more reliably for documents with names such as PDFs from shared portals or internal knowledge bases.
- Improved all assistants, so running tasks can continue when you leave the assistant and return later.
- Improved the assistant overview so it shows which assistants are still running or have a result ready.
- Improved the activity indicators for running chats and assistants so they use a consistent blue highlight.