mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 18:07:21 +00:00
Added profile, provider, and workspace preselection to chat templates (#1099)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
1 parent
ab9df99cf0
commit
24c4c31035
34 files changed
+2084
-142
No files matched your search
@@ -50,7 +50,47 @@ public record ChatTemplate(
|
||||
/// whether the retrieved data is validated.
|
||||
/// </remarks>
|
||||
public DataSourceOptions? DataSourceOptions { get; init; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The profile this template preselects for a chat started with it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null means the template says nothing about profiles, so the chat keeps the profile it would
|
||||
/// start with anyway: the default of the chat settings, or the one the user picked. The ID of
|
||||
/// Profile.NO_PROFILE is a statement of its own: such chats start without a profile, whatever
|
||||
/// that default says.<br/><br/>
|
||||
/// A preselection, not a limit: the user picks another profile in the chat as usual. It means
|
||||
/// nothing for a template which does not allow profiles, and a profile which is gone leaves the
|
||||
/// chat default in place.
|
||||
/// </remarks>
|
||||
public string? ProfileId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider, and with it the model, this template preselects for a chat started with it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null means the template says nothing, so the chat starts with the provider it would start
|
||||
/// with anyway. A preselection, not a limit, and no way around the confidence levels either: a
|
||||
/// provider which is gone, or which may not be used for chats, leaves the chat to start as if
|
||||
/// the template said nothing.
|
||||
/// </remarks>
|
||||
public string? ProviderId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the workspace a chat started with this template is kept in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null means the template says nothing, so the chat goes where it would go anyway. A name
|
||||
/// rather than an ID: a workspace has an ID of its own on every computer, so a configuration
|
||||
/// plugin has no other way to name one. Like for an assistant chat launcher, the workspace is
|
||||
/// created when it does not exist yet; on the chat page only with the first message of the chat,
|
||||
/// so that trying out a template creates no workspace.<br/><br/>
|
||||
/// It only fills in where a chat would belong to no workspace otherwise. A chat started in a
|
||||
/// workspace on purpose, for example from the workspace list, stays there, and so does every
|
||||
/// chat while workspaces are switched off.
|
||||
/// </remarks>
|
||||
public string? WorkspaceName { get; init; }
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ChatTemplate).Namespace, nameof(ChatTemplate));
|
||||
|
||||
private static readonly ILogger<ChatTemplate> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ChatTemplate>();
|
||||
@@ -67,6 +107,9 @@ public record ChatTemplate(
|
||||
AllowProfileUsage = true,
|
||||
ToolIds = null,
|
||||
DataSourceOptions = null,
|
||||
ProfileId = null,
|
||||
ProviderId = null,
|
||||
WorkspaceName = null,
|
||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||
IsEnterpriseConfiguration = false,
|
||||
};
|
||||
@@ -81,6 +124,16 @@ public record ChatTemplate(
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Whether this template starts its chats without a profile, whatever the default says.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// That is a statement of its own, made with the ID of the no-profile entry, see ProfileId. It
|
||||
/// names no profile which could be missing on another computer. A method rather than a property,
|
||||
/// so that it does not end up in the stored settings.
|
||||
/// </remarks>
|
||||
public bool StartsWithoutProfile() => string.Equals(this.ProfileId, Profile.NO_PROFILE.Id, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this chat template. If it is the NO_CHAT_TEMPLATE, it returns a localized string.
|
||||
/// </summary>
|
||||
@@ -117,12 +170,12 @@ public record ChatTemplate(
|
||||
/// <param name="chatTemplate">The chat template the launcher opens its chat with.</param>
|
||||
/// <param name="launcherToolIds">The tools the launcher names itself, or null when it names none.</param>
|
||||
/// <returns>The tools to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for it.</returns>
|
||||
public static (IReadOnlyCollection<string>? ToolIds, bool LauncherChoiceDropped) ChooseToolIds(ChatTemplate chatTemplate, IReadOnlyCollection<string>? launcherToolIds)
|
||||
public static ChatTemplateToolChoice ChooseToolIds(ChatTemplate chatTemplate, IReadOnlyCollection<string>? launcherToolIds)
|
||||
{
|
||||
if (chatTemplate.ToolIds is not { } templateToolIds)
|
||||
return (launcherToolIds, false);
|
||||
return new(launcherToolIds, false);
|
||||
|
||||
return (templateToolIds, launcherToolIds is not null);
|
||||
return new(templateToolIds, launcherToolIds is not null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,12 +190,12 @@ public record ChatTemplate(
|
||||
/// <param name="chatTemplate">The chat template the launcher opens its chat with.</param>
|
||||
/// <param name="launcherOptions">The options built from the data sources the launcher names, or null when it names none.</param>
|
||||
/// <returns>The options to start with — null when neither says anything, which leaves the chat default in place — and whether the launcher's own choice was dropped for them.</returns>
|
||||
public static (DataSourceOptions? Options, bool LauncherChoiceDropped) ChooseDataSourceOptions(ChatTemplate chatTemplate, DataSourceOptions? launcherOptions)
|
||||
public static ChatTemplateDataSourceChoice ChooseDataSourceOptions(ChatTemplate chatTemplate, DataSourceOptions? launcherOptions)
|
||||
{
|
||||
if (chatTemplate.DataSourceOptions is not { } templateOptions)
|
||||
return (launcherOptions, false);
|
||||
return new(launcherOptions, false);
|
||||
|
||||
return (templateOptions.CreateCopy(), launcherOptions is not null);
|
||||
return new(templateOptions.CreateCopy(), launcherOptions is not null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,6 +255,10 @@ public record ChatTemplate(
|
||||
|
||||
var fileAttachments = ParseFileAttachments(idx, table, pluginPath);
|
||||
|
||||
var profileId = ParseOptionalId(idx, table, "ProfileId", allowEmpty: true);
|
||||
if (profileId is not null && !allowProfileUsage)
|
||||
LOGGER.LogWarning("The chat template {IdxChatTemplate} preselects a profile but does not allow profiles. Chats started with it ignore that profile.", idx);
|
||||
|
||||
template = new ChatTemplate
|
||||
{
|
||||
Num = 0, // will be set later by the PluginConfigurationObject
|
||||
@@ -214,6 +271,9 @@ public record ChatTemplate(
|
||||
AllowProfileUsage = allowProfileUsage,
|
||||
ToolIds = ParseToolIds(idx, table),
|
||||
DataSourceOptions = ParseDataSourceOptions(idx, table),
|
||||
ProfileId = profileId,
|
||||
ProviderId = ParseOptionalId(idx, table, "ProviderId", allowEmpty: false),
|
||||
WorkspaceName = ParseWorkspaceName(idx, table),
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = configPluginId,
|
||||
};
|
||||
@@ -294,6 +354,43 @@ public record ChatTemplate(
|
||||
return toolIds;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The empty GUID is a statement for a profile — start without one — but names no provider at
|
||||
/// all, hence the switch. Anything which is no GUID cannot point at a provider or profile and is
|
||||
/// dropped, so that the template still works, with the chat defaults in its place.
|
||||
/// </remarks>
|
||||
private static string? ParseOptionalId(int idx, LuaTable table, string fieldName, bool allowEmpty)
|
||||
{
|
||||
if (!table.TryGetValue(fieldName, out var idValue))
|
||||
return null;
|
||||
|
||||
if (!idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id) || (!allowEmpty && id == Guid.Empty))
|
||||
{
|
||||
LOGGER.LogWarning("The {FieldName} of chat template {IdxChatTemplate} is not a valid ID and will be ignored.", fieldName, idx);
|
||||
return null;
|
||||
}
|
||||
|
||||
return id.ToString();
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// An empty name would mean "no workspace", which is what a template says by leaving the field
|
||||
/// out. So it is dropped, with a warning, as is anything which is no text.
|
||||
/// </remarks>
|
||||
private static string? ParseWorkspaceName(int idx, LuaTable table)
|
||||
{
|
||||
if (!table.TryGetValue("WorkspaceName", out var workspaceNameValue))
|
||||
return null;
|
||||
|
||||
if (!workspaceNameValue.TryRead<string>(out var workspaceName) || string.IsNullOrWhiteSpace(workspaceName))
|
||||
{
|
||||
LOGGER.LogWarning("The WorkspaceName of chat template {IdxChatTemplate} is not a valid name and will be ignored.", idx);
|
||||
return null;
|
||||
}
|
||||
|
||||
return workspaceName.Trim();
|
||||
}
|
||||
|
||||
private static DataSourceOptions? ParseDataSourceOptions(int idx, LuaTable table)
|
||||
{
|
||||
if (!table.TryGetValue("DataSourceOptions", out var optionsValue) || !optionsValue.TryRead<LuaTable>(out var optionsTable))
|
||||
@@ -449,22 +546,25 @@ public record ChatTemplate(
|
||||
var fileAttachmentsLua = this.BuildFileAttachmentsLua(fileAttachmentPaths);
|
||||
|
||||
//
|
||||
// Both of these may be absent entirely, because saying nothing about tools or data sources
|
||||
// is a statement of its own. They therefore bring their own line break and indentation
|
||||
// instead of sitting on a line of the template:
|
||||
// These may be absent entirely, because saying nothing about the profile, the provider, the
|
||||
// workspace, the tools, or the data sources is a statement of its own. They therefore bring
|
||||
// their own line break and indentation instead of sitting on a line of the template:
|
||||
//
|
||||
var profileIdLua = BuildOptionalStringLua("ProfileId", this.ProfileId);
|
||||
var providerIdLua = BuildOptionalStringLua("ProviderId", this.ProviderId);
|
||||
var workspaceNameLua = BuildOptionalStringLua("WorkspaceName", this.WorkspaceName);
|
||||
var toolIdsLua = this.BuildToolIdsLua();
|
||||
var dataSourceOptionsLua = this.BuildDataSourceOptionsLua();
|
||||
|
||||
luaCode = $$"""
|
||||
{{this.BuildDataSourceIdNote()}}CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
{{this.BuildProviderAndProfileIdNote()}}{{this.BuildDataSourceIdNote()}}CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
["Id"] = "{{LuaTools.EscapeLuaString(exportId)}}",
|
||||
["Name"] = {{LuaTools.ToLuaStringLiteral(this.Name)}},
|
||||
["SystemPrompt"] = {{LuaTools.ToLuaStringLiteral(this.SystemPrompt)}},
|
||||
["PredefinedUserPrompt"] = {{LuaTools.ToLuaStringLiteral(this.PredefinedUserPrompt)}},
|
||||
["AllowProfileUsage"] = {{this.AllowProfileUsage.ToString().ToLowerInvariant()}},
|
||||
["FileAttachments"] = {{fileAttachmentsLua}},
|
||||
["ExampleConversation"] = {{exampleConversationLua}},{{toolIdsLua}}{{dataSourceOptionsLua}}
|
||||
["ExampleConversation"] = {{exampleConversationLua}},{{profileIdLua}}{{providerIdLua}}{{workspaceNameLua}}{{toolIdsLua}}{{dataSourceOptionsLua}}
|
||||
}
|
||||
""";
|
||||
return true;
|
||||
@@ -574,6 +674,10 @@ public record ChatTemplate(
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string BuildOptionalStringLua(string fieldName, string? value) => value is null
|
||||
? string.Empty
|
||||
: $"{Environment.NewLine} [\"{fieldName}\"] = {LuaTools.ToLuaStringLiteral(value)},";
|
||||
|
||||
/// <remarks>
|
||||
/// An empty set is written out as an empty table rather than being left out: the two say
|
||||
/// different things, and dropping the line would turn "no tools at all" into "whatever the
|
||||
@@ -653,6 +757,26 @@ public record ChatTemplate(
|
||||
""";
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The same holds for the provider and the profile: a configuration plugin reaches only those
|
||||
/// which it or another plugin defines, and the ones of this machine are mostly the user's own.
|
||||
/// Starting without a profile names nothing, so it needs no note.
|
||||
/// </remarks>
|
||||
private string BuildProviderAndProfileIdNote()
|
||||
{
|
||||
var namesProfile = this.ProfileId is not null && !this.StartsWithoutProfile();
|
||||
if (this.ProviderId is null && !namesProfile)
|
||||
return string.Empty;
|
||||
|
||||
return """
|
||||
-- The provider and profile IDs below are the ones of the machine this was exported from.
|
||||
-- Please check them against your CONFIG["LLM_PROVIDERS"] and CONFIG["PROFILES"]: an ID
|
||||
-- which resolves to nothing is ignored, and a chat with this template then starts with
|
||||
-- the provider or profile it would start with anyway.
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string BuildFileAttachmentsLua(IReadOnlyList<string>? fileAttachmentPaths)
|
||||
{
|
||||
var paths = fileAttachmentPaths ?? this.FileAttachments.Select(attachment => attachment.FilePath).ToList();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Whose data source options a chat started by a launcher begins with, decided by ChatTemplate.ChooseDataSourceOptions.
|
||||
/// </summary>
|
||||
/// <param name="Options">The options to start with, or null when neither side names any, which leaves the chat default in place.</param>
|
||||
/// <param name="LauncherChoiceDropped">Whether the data sources the launcher names itself were dropped for the options of the chat template.</param>
|
||||
public readonly record struct ChatTemplateDataSourceChoice(DataSourceOptions? Options, bool LauncherChoiceDropped)
|
||||
{
|
||||
/// <summary>
|
||||
/// Neither side names any data sources, so the chat keeps its default options.
|
||||
/// </summary>
|
||||
public static readonly ChatTemplateDataSourceChoice NONE = new(null, false);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Whose tools a chat started by a launcher begins with, decided by ChatTemplate.ChooseToolIds.
|
||||
/// </summary>
|
||||
/// <param name="ToolIds">The tools to start with, or null when neither side names any, which leaves the chat default in place.</param>
|
||||
/// <param name="LauncherChoiceDropped">Whether the tools the launcher names itself were dropped for those of the chat template.</param>
|
||||
public readonly record struct ChatTemplateToolChoice(IReadOnlyCollection<string>? ToolIds, bool LauncherChoiceDropped)
|
||||
{
|
||||
/// <summary>
|
||||
/// Neither side names any tools, so the chat keeps its default.
|
||||
/// </summary>
|
||||
public static readonly ChatTemplateToolChoice NONE = new(null, false);
|
||||
}
|
||||
@@ -877,6 +877,149 @@ public sealed class SettingsManager
|
||||
return this.GetChatTemplateById(this.ConfigurationData.App.PreselectedChatTemplate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the profile a chat template preselects for the chats started with it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null whenever the template has nothing to say which could be used: it names no profile, it
|
||||
/// does not allow profiles, or the profile it names is gone. The chat then keeps the profile it
|
||||
/// would have without the template. Starting without a profile is an answer of its own, and it
|
||||
/// comes back as Profile.NO_PROFILE.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <returns>The profile to start with, or null to keep the usual one.</returns>
|
||||
public Profile? GetChatTemplateProfile(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!chatTemplate.AllowProfileUsage || chatTemplate.ProfileId is null)
|
||||
return null;
|
||||
|
||||
if (chatTemplate.StartsWithoutProfile())
|
||||
return Profile.NO_PROFILE;
|
||||
|
||||
var profile = this.GetProfileById(chatTemplate.ProfileId);
|
||||
return profile == Profile.NO_PROFILE ? null : profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the profile a chat started with the given chat template begins with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null when the template says nothing about profiles, so the chat keeps the profile it has.
|
||||
/// A template whose profile is gone starts its chats with the default profile of the chat
|
||||
/// settings, which may be none — not with whatever profile the previous chat happened to use,
|
||||
/// which would be a choice nobody made for this chat.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <returns>The profile to start with, or null to keep the current one.</returns>
|
||||
public Profile? GetChatTemplateStartProfile(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (!chatTemplate.AllowProfileUsage || chatTemplate.ProfileId is null)
|
||||
return null;
|
||||
|
||||
return this.GetChatTemplateProfile(chatTemplate) ?? this.GetPreselectedProfile(Tools.Components.CHAT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the provider a chat template preselects for the chats started with it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The template is no way around the confidence levels: a provider which may not be used for
|
||||
/// chats comes back as Provider.NONE, just like one which is gone or one the
|
||||
/// template does not name at all. The chat then keeps the provider it would have without the
|
||||
/// template.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <returns>The provider to start with, or Provider.NONE to keep the usual one.</returns>
|
||||
public Provider GetChatTemplateProvider(ChatTemplate chatTemplate) => this.GetChatTemplateProvider(chatTemplate.ProviderId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the provider a chat template may preselect under the given ID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The one rule for the provider of a chat template: it exists and meets the confidence level
|
||||
/// required for chats. The chat, assistant chat launchers, the chat template dialog, and the
|
||||
/// import of a chat template all ask here, so that none of them accepts a provider another
|
||||
/// one would leave out.
|
||||
/// </remarks>
|
||||
/// <param name="providerId">The ID the chat template names, or null when it names none.</param>
|
||||
/// <returns>The provider, or Provider.NONE when the ID names no provider a chat may use.</returns>
|
||||
public Provider GetChatTemplateProvider(string? providerId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
return Provider.NONE;
|
||||
|
||||
var provider = this.GetProviderById(providerId);
|
||||
return provider != Provider.NONE && this.IsProviderConfident(provider, Tools.Components.CHAT) ? provider : Provider.NONE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the profile a chat begins with when it starts with the given chat template now.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A template which names a profile decides, see GetChatTemplateStartProfile. One
|
||||
/// which says nothing keeps the current profile, with one exception: when that profile is the one
|
||||
/// the previous template preselected, the chat starts with the default profile of the chat
|
||||
/// settings instead. Keeping it would carry the choice of another template over into this chat.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <param name="previousChatTemplate">The chat template the current profile was chosen with; the same one when the template does not change.</param>
|
||||
/// <param name="currentProfile">The profile the chat has right now.</param>
|
||||
/// <returns>The profile to start with.</returns>
|
||||
public Profile ChooseChatTemplateProfile(ChatTemplate chatTemplate, ChatTemplate previousChatTemplate, Profile currentProfile)
|
||||
{
|
||||
if (this.GetChatTemplateStartProfile(chatTemplate) is { } templateProfile)
|
||||
return templateProfile;
|
||||
|
||||
if (this.GetChatTemplateStartProfile(previousChatTemplate) is { } previousTemplateProfile && previousTemplateProfile.Id == currentProfile.Id)
|
||||
return this.GetPreselectedProfile(Tools.Components.CHAT);
|
||||
|
||||
return currentProfile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the provider a chat begins with when it starts with the given chat template now.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as for the profile, see ChooseChatTemplateProfile: a template which
|
||||
/// names a usable provider decides, and the provider of the previous template is not carried
|
||||
/// over. In its place comes the default provider of the chat settings. Without such a default,
|
||||
/// the current provider stays, so that the chat does not lose its provider altogether.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <param name="previousChatTemplate">The chat template the current provider was chosen with; the same one when the template does not change.</param>
|
||||
/// <param name="currentProvider">The provider the chat has right now.</param>
|
||||
/// <returns>The provider to start with.</returns>
|
||||
public Provider ChooseChatTemplateProvider(ChatTemplate chatTemplate, ChatTemplate previousChatTemplate, Provider currentProvider)
|
||||
{
|
||||
var templateProvider = this.GetChatTemplateProvider(chatTemplate);
|
||||
if (templateProvider != Provider.NONE)
|
||||
return templateProvider;
|
||||
|
||||
var previousTemplateProvider = this.GetChatTemplateProvider(previousChatTemplate);
|
||||
if (previousTemplateProvider == Provider.NONE || previousTemplateProvider.Id != currentProvider.Id)
|
||||
return currentProvider;
|
||||
|
||||
var defaultProvider = this.GetPreselectedProvider(Tools.Components.CHAT);
|
||||
return defaultProvider == Provider.NONE ? currentProvider : defaultProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the workspace a chat template keeps the chats started with it in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null when the template names none, and also while workspaces are switched off: a chat then
|
||||
/// belongs to no workspace, whatever its template says.
|
||||
/// </remarks>
|
||||
/// <param name="chatTemplate">The chat template of the chat about to start.</param>
|
||||
/// <returns>The trimmed name of the workspace, or null to keep the chat where it would go anyway.</returns>
|
||||
public string? GetChatTemplateWorkspaceName(ChatTemplate chatTemplate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(chatTemplate.WorkspaceName) || this.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.DISABLE_WORKSPACES)
|
||||
return null;
|
||||
|
||||
return chatTemplate.WorkspaceName.Trim();
|
||||
}
|
||||
|
||||
public Profile GetProfileById(string? profileId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(profileId))
|
||||
|
||||
Reference in new issue
Block a user