mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
96 changed files with 1224 additions and 201 deletions
@ -0,0 +1,59 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents; |
||||
|
|
||||
|
public static class Updates |
||||
|
{ |
||||
|
public static bool IsUnset(object? value) |
||||
|
{ |
||||
|
if (value is JsonValue json) |
||||
|
{ |
||||
|
return IsUnset(json.Value); |
||||
|
} |
||||
|
|
||||
|
return value is IReadOnlyDictionary<string, JsonValue> obj && IsUnset(obj); |
||||
|
} |
||||
|
|
||||
|
public static bool IsUnset(IReadOnlyDictionary<string, JsonValue>? obj) |
||||
|
{ |
||||
|
return |
||||
|
obj is { Count: 1 } && |
||||
|
obj.TryGetValue("$unset", out var item) && |
||||
|
Equals(item.Value, true); |
||||
|
} |
||||
|
|
||||
|
public static bool IsUpdate(object? value, out string expression) |
||||
|
{ |
||||
|
expression = null!; |
||||
|
|
||||
|
if (value is JsonValue json) |
||||
|
{ |
||||
|
return IsUpdate(json.Value, out expression); |
||||
|
} |
||||
|
|
||||
|
return value is IReadOnlyDictionary<string, JsonValue> obj && IsUpdate(obj, out expression); |
||||
|
} |
||||
|
|
||||
|
public static bool IsUpdate(IReadOnlyDictionary<string, JsonValue>? obj, out string expression) |
||||
|
{ |
||||
|
expression = null!; |
||||
|
|
||||
|
if (obj is { Count: > 0 } && |
||||
|
obj.TryGetValue("$update", out var item) && |
||||
|
item.Value is string e && |
||||
|
!string.IsNullOrWhiteSpace(e)) |
||||
|
{ |
||||
|
expression = e; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.AI; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities; |
||||
|
|
||||
|
public sealed class AppChatContext : ChatContext |
||||
|
{ |
||||
|
required public Context BaseContext { get; init; } |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Squidex.AI; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps; |
||||
|
|
||||
|
public sealed class AppChatTools : IChatToolProvider |
||||
|
{ |
||||
|
private readonly IJsonSerializer serializer; |
||||
|
private readonly IUrlGenerator urlGenerator; |
||||
|
|
||||
|
public AppChatTools(IJsonSerializer serializer, IUrlGenerator urlGenerator) |
||||
|
{ |
||||
|
this.serializer = serializer; |
||||
|
this.urlGenerator = urlGenerator; |
||||
|
} |
||||
|
|
||||
|
public async IAsyncEnumerable<IChatTool> GetToolsAsync(ChatContext chatContext, |
||||
|
[EnumeratorCancellation] CancellationToken ct) |
||||
|
{ |
||||
|
if (chatContext is not AppChatContext appContext) |
||||
|
{ |
||||
|
yield break; |
||||
|
} |
||||
|
|
||||
|
var context = appContext.BaseContext; |
||||
|
|
||||
|
await Task.Yield(); |
||||
|
|
||||
|
if (context.Allows(PermissionIds.AppClientsRead)) |
||||
|
{ |
||||
|
yield return new DelegateChatTool( |
||||
|
new ToolSpec("clients", "Clients", "Provides the clients for the Squidex App."), |
||||
|
(_, ct) => |
||||
|
{ |
||||
|
var result = new |
||||
|
{ |
||||
|
Clients = context.App.Clients.Select(x => |
||||
|
new |
||||
|
{ |
||||
|
Id = x.Key, |
||||
|
ClientId = $"{context.App.Name}:{x.Key}", |
||||
|
ClientSecret = "obfuscated", |
||||
|
x.Value.Role |
||||
|
}), |
||||
|
Url = urlGenerator.ClientsUI(context.App.NamedId()) |
||||
|
}; |
||||
|
|
||||
|
var json = serializer.Serialize(result, true); |
||||
|
|
||||
|
return Task.FromResult(json); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (context.Allows(PermissionIds.AppLanguagesRead)) |
||||
|
{ |
||||
|
yield return new DelegateChatTool( |
||||
|
new ToolSpec("languages", "Languages", "Provides the languages for the Squidex App."), |
||||
|
(_, ct) => |
||||
|
{ |
||||
|
var result = new |
||||
|
{ |
||||
|
Languages = context.App.Languages.Values.Select(x => |
||||
|
new |
||||
|
{ |
||||
|
Iso2Code = x.Key, |
||||
|
IsMaster = context.App.Languages.Master.Equals(x.Key), |
||||
|
x.Value.IsOptional |
||||
|
}), |
||||
|
Url = urlGenerator.LanguagesUI(context.App.NamedId()) |
||||
|
}; |
||||
|
|
||||
|
var json = serializer.Serialize(result, true); |
||||
|
|
||||
|
return Task.FromResult(json); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (context.Allows(PermissionIds.AppRolesRead)) |
||||
|
{ |
||||
|
yield return new DelegateChatTool( |
||||
|
new ToolSpec("roles", "Roles", "Provides the roles for the Squidex App."), |
||||
|
(_, ct) => |
||||
|
{ |
||||
|
var result = new |
||||
|
{ |
||||
|
Roles = context.App.Roles.Custom.Select(x => |
||||
|
new |
||||
|
{ |
||||
|
x.Name |
||||
|
}), |
||||
|
Url = urlGenerator.RolesUI(context.App.NamedId()) |
||||
|
}; |
||||
|
|
||||
|
var json = serializer.Serialize(result, true); |
||||
|
|
||||
|
return Task.FromResult(json); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (context.Allows(PermissionIds.AppPlansRead)) |
||||
|
{ |
||||
|
yield return new DelegateChatTool( |
||||
|
new ToolSpec("plan", "Plan", "Provides the plan for the Squidex App."), |
||||
|
(_, ct) => |
||||
|
{ |
||||
|
var result = new |
||||
|
{ |
||||
|
Plan = new |
||||
|
{ |
||||
|
Name = context.App.Plan?.PlanId, |
||||
|
}, |
||||
|
Url = urlGenerator.PlansUI(context.App.NamedId()) |
||||
|
}; |
||||
|
|
||||
|
var json = serializer.Serialize(result, true); |
||||
|
|
||||
|
return Task.FromResult(json); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Squidex.AI; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
public sealed class SchemasChatTool : IChatToolProvider |
||||
|
{ |
||||
|
private readonly IAppProvider appProvider; |
||||
|
private readonly IJsonSerializer serializer; |
||||
|
private readonly IUrlGenerator urlGenerator; |
||||
|
|
||||
|
public SchemasChatTool(IAppProvider appProvider, IJsonSerializer serializer, IUrlGenerator urlGenerator) |
||||
|
{ |
||||
|
this.appProvider = appProvider; |
||||
|
this.serializer = serializer; |
||||
|
this.urlGenerator = urlGenerator; |
||||
|
} |
||||
|
|
||||
|
public async IAsyncEnumerable<IChatTool> GetToolsAsync(ChatContext chatContext, |
||||
|
[EnumeratorCancellation] CancellationToken ct) |
||||
|
{ |
||||
|
if (chatContext is not AppChatContext appContext) |
||||
|
{ |
||||
|
yield break; |
||||
|
} |
||||
|
|
||||
|
var context = appContext.BaseContext; |
||||
|
|
||||
|
await Task.Yield(); |
||||
|
|
||||
|
if (context.Allows(PermissionIds.AppSchemasRead)) |
||||
|
{ |
||||
|
yield return new DelegateChatTool( |
||||
|
new ToolSpec("schemas", "Schemas", "Provides the schemas for the Squidex App."), |
||||
|
async (_, ct) => |
||||
|
{ |
||||
|
var schemas = await appProvider.GetSchemasAsync(context.App.Id, ct); |
||||
|
|
||||
|
var result = new |
||||
|
{ |
||||
|
Schemas = schemas.Select(x => |
||||
|
new |
||||
|
{ |
||||
|
x.Name, |
||||
|
x.IsPublished, |
||||
|
x.Type, |
||||
|
FieldCount = x.Fields.Count, |
||||
|
Url = urlGenerator.SchemaUI(context.App.NamedId(), x.NamedId()) |
||||
|
}), |
||||
|
Url = urlGenerator.SchemasUI(context.App.NamedId()) |
||||
|
}; |
||||
|
|
||||
|
var json = serializer.Serialize(result, true); |
||||
|
|
||||
|
return json; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.TestHelpers; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps; |
||||
|
|
||||
|
public class AppChatToolsTests : GivenContext |
||||
|
{ |
||||
|
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); |
||||
|
private readonly AppChatTools sut; |
||||
|
|
||||
|
public AppChatToolsTests() |
||||
|
{ |
||||
|
sut = new AppChatTools(TestUtils.DefaultSerializer, urlGenerator); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_clients_if_user_has_permission() |
||||
|
{ |
||||
|
App = App with |
||||
|
{ |
||||
|
Clients = App.Clients.Add("default", "secret") |
||||
|
}; |
||||
|
|
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = CreateContext(PermissionIds.ForApp(PermissionIds.AppClientsRead, App.Name).Id) |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.NotNull(tool); |
||||
|
Assert.Equal("clients", tool.Spec.Name); |
||||
|
Assert.Equal("Clients", tool.Spec.DisplayName); |
||||
|
|
||||
|
var result = await tool.ExecuteAsync(null!, default); |
||||
|
|
||||
|
Assert.Contains($"{App.Name}:default", result); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.ClientsUI(AppId)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_languages_if_user_has_permission() |
||||
|
{ |
||||
|
App = App with |
||||
|
{ |
||||
|
Languages = App.Languages.Set(Language.DE) |
||||
|
}; |
||||
|
|
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = CreateContext(PermissionIds.ForApp(PermissionIds.AppLanguages, App.Name).Id) |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.NotNull(tool); |
||||
|
Assert.Equal("languages", tool.Spec.Name); |
||||
|
Assert.Equal("Languages", tool.Spec.DisplayName); |
||||
|
|
||||
|
var result = await tool.ExecuteAsync(null!, default); |
||||
|
|
||||
|
Assert.Contains($"\"de\"", result); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.LanguagesUI(AppId)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_roles_if_user_has_permission() |
||||
|
{ |
||||
|
App = App with |
||||
|
{ |
||||
|
Roles = App.Roles.Add("viewers") |
||||
|
}; |
||||
|
|
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = CreateContext(PermissionIds.ForApp(PermissionIds.AppRoles, App.Name).Id) |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.NotNull(tool); |
||||
|
Assert.Equal("roles", tool.Spec.Name); |
||||
|
Assert.Equal("Roles", tool.Spec.DisplayName); |
||||
|
|
||||
|
var result = await tool.ExecuteAsync(null!, default); |
||||
|
|
||||
|
Assert.Contains($"viewers", result); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.RolesUI(AppId)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_plan_if_user_has_permission() |
||||
|
{ |
||||
|
App = App with |
||||
|
{ |
||||
|
Plan = new AssignedPlan(User, "Business") |
||||
|
}; |
||||
|
|
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = CreateContext(PermissionIds.ForApp(PermissionIds.AppPlans, App.Name).Id) |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.NotNull(tool); |
||||
|
Assert.Equal("plan", tool.Spec.Name); |
||||
|
Assert.Equal("Plan", tool.Spec.DisplayName); |
||||
|
|
||||
|
var result = await tool.ExecuteAsync(null!, default); |
||||
|
|
||||
|
Assert.Contains($"Business", result); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.PlansUI(AppId)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_return_tools_if_user_no_permission() |
||||
|
{ |
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = FrontendContext |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.Null(tool); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using MongoDB.Bson.Serialization; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Domain.Apps.Entities.MongoDb.Contents; |
||||
|
using ExtensionSut = Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations.Extensions; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; |
||||
|
|
||||
|
public class ExtensionsTests |
||||
|
{ |
||||
|
public ExtensionsTests() |
||||
|
{ |
||||
|
MongoContentEntity.RegisterClassMap(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_build_projection_without_fields() |
||||
|
{ |
||||
|
var projection = ExtensionSut.BuildProjection2<MongoContentEntity>(null); |
||||
|
|
||||
|
AssertProjection(projection, "{ 'dd' : 0 }"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_build_projection_with_data_prefix() |
||||
|
{ |
||||
|
var projection = ExtensionSut.BuildProjection2<MongoContentEntity>(["data.myField"]); |
||||
|
|
||||
|
AssertProjection(projection, "{ '_ai' : 1, '_id' : 1, '_si' : 1, 'ai' : 1, 'cb' : 1, 'ct' : 1, 'dl' : 1, 'do.myField' : 1, 'id' : 1, 'is' : 1, 'mb' : 1, 'mt' : 1, 'ns' : 1, 'rf' : 1, 'sa' : 1, 'si' : 1, 'sj' : 1, 'ss' : 1, 'ts' : 1, 'vs' : 1 }"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_build_projection_without_data_prefix() |
||||
|
{ |
||||
|
var projection = ExtensionSut.BuildProjection2<MongoContentEntity>(["myField"]); |
||||
|
|
||||
|
AssertProjection(projection, "{ '_ai' : 1, '_id' : 1, '_si' : 1, 'ai' : 1, 'cb' : 1, 'ct' : 1, 'dl' : 1, 'do.myField' : 1, 'id' : 1, 'is' : 1, 'mb' : 1, 'mt' : 1, 'ns' : 1, 'rf' : 1, 'sa' : 1, 'si' : 1, 'sj' : 1, 'ss' : 1, 'ts' : 1, 'vs' : 1 }"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_build_projection_without_included_field() |
||||
|
{ |
||||
|
var projection = ExtensionSut.BuildProjection2<MongoContentEntity>(["myField.special", "myField"]); |
||||
|
|
||||
|
AssertProjection(projection, "{ '_ai' : 1, '_id' : 1, '_si' : 1, 'ai' : 1, 'cb' : 1, 'ct' : 1, 'dl' : 1, 'do.myField' : 1, 'id' : 1, 'is' : 1, 'mb' : 1, 'mt' : 1, 'ns' : 1, 'rf' : 1, 'sa' : 1, 'si' : 1, 'sj' : 1, 'ss' : 1, 'ts' : 1, 'vs' : 1 }"); |
||||
|
} |
||||
|
|
||||
|
private static void AssertProjection(ProjectionDefinition<MongoContentEntity, MongoContentEntity> projection, string expected) |
||||
|
{ |
||||
|
var rendered = |
||||
|
projection.Render( |
||||
|
BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>(), |
||||
|
BsonSerializer.SerializerRegistry) |
||||
|
.Document.ToString(); |
||||
|
|
||||
|
Assert.Equal(Cleanup(expected), rendered); |
||||
|
} |
||||
|
|
||||
|
private static string Cleanup(string filter) |
||||
|
{ |
||||
|
return filter.Replace('\'', '"'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.TestHelpers; |
||||
|
using Squidex.Domain.Apps.Entities.TestHelpers; |
||||
|
using Squidex.Shared; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
public class SchemasChatToolTests : GivenContext |
||||
|
{ |
||||
|
private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>(); |
||||
|
private readonly SchemasChatTool sut; |
||||
|
|
||||
|
public SchemasChatToolTests() |
||||
|
{ |
||||
|
sut = new SchemasChatTool(AppProvider, TestUtils.DefaultSerializer, urlGenerator); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_schemas_if_user_has_permission() |
||||
|
{ |
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = CreateContext(PermissionIds.ForApp(PermissionIds.AppSchemasRead, App.Name).Id) |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.NotNull(tool); |
||||
|
Assert.Equal("schemas", tool.Spec.Name); |
||||
|
Assert.Equal("Schemas", tool.Spec.DisplayName); |
||||
|
|
||||
|
var result = await tool.ExecuteAsync(null!, default); |
||||
|
|
||||
|
Assert.Contains(Schema.Name, result); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.SchemasUI(AppId)) |
||||
|
.MustHaveHappened(); |
||||
|
|
||||
|
A.CallTo(() => urlGenerator.SchemaUI(AppId, SchemaId)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_return_tools_if_user_no_permission() |
||||
|
{ |
||||
|
var chatContext = new AppChatContext |
||||
|
{ |
||||
|
BaseContext = FrontendContext |
||||
|
}; |
||||
|
|
||||
|
var tool = await sut.GetToolsAsync(chatContext, default).FirstOrDefaultAsync(); |
||||
|
|
||||
|
Assert.Null(tool); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
@if ((appsState.selectedApp | async) && hasChatBot) { |
||||
|
<ul class="nav navbar-nav align-items-center"> |
||||
|
<li class="nav-item nav-icon" (click)="chatDialog.show()"> |
||||
|
<span class="nav-link">AI</span> |
||||
|
</li> |
||||
|
</ul> |
||||
|
} |
||||
|
|
||||
|
<sqx-chat-dialog (contentSelect)="chatDialog.hide()" *sqxModal="chatDialog"></sqx-chat-dialog> |
||||
@ -0,0 +1,7 @@ |
|||||
|
@import 'mixins'; |
||||
|
@import 'vars'; |
||||
|
|
||||
|
.nav-link { |
||||
|
font-weight: 450; |
||||
|
font-size: 1.3rem; |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { AsyncPipe } from '@angular/common'; |
||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; |
||||
|
import { AppsState, ChatDialogComponent, DialogModel, ModalDirective, UIOptions } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
standalone: true, |
||||
|
selector: 'sqx-chat-menu', |
||||
|
styleUrls: ['./chat-menu.component.scss'], |
||||
|
templateUrl: './chat-menu.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
imports: [ |
||||
|
AsyncPipe, |
||||
|
ChatDialogComponent, |
||||
|
ModalDirective |
||||
|
], |
||||
|
}) |
||||
|
export class ChatMenuComponent { |
||||
|
public readonly chatDialog = new DialogModel(); |
||||
|
|
||||
|
public readonly hasChatBot = inject(UIOptions).value.canUseChatBot; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly appsState: AppsState |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue