mirror of https://github.com/Squidex/squidex.git
Browse Source
* Fix prepare step. * UI fixes. * Small fixes. * Generate with AI. * Update packages.pull/1241/head
committed by
GitHub
106 changed files with 1592 additions and 804 deletions
@ -0,0 +1,79 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text; |
|||
using IdentityModel; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Newtonsoft.Json; |
|||
using Squidex.CLI.Commands.Implementation.AI; |
|||
using Squidex.Infrastructure.ObjectPool; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Templates; |
|||
|
|||
public sealed class AIQueryCache(IDistributedCache distributedCache) : IQueryCache |
|||
{ |
|||
private readonly DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(30), |
|||
}; |
|||
|
|||
public async Task<GeneratedContent?> GetAsync(string prompt, |
|||
CancellationToken ct = default) |
|||
{ |
|||
var cached = await distributedCache.GetAsync(CacheKey(prompt), ct); |
|||
if (cached == null) |
|||
{ |
|||
return default!; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
// Use newtonsoft JSON because the CLI still deals with this library and uses JTokens.
|
|||
using var cacheStream = new MemoryStream(cached); |
|||
using var cacheReader = new StreamReader(cacheStream); |
|||
using var jsonReader = new JsonTextReader(cacheReader); |
|||
|
|||
var serializer = new JsonSerializer(); |
|||
return serializer.Deserialize<GeneratedContent>(jsonReader); |
|||
} |
|||
catch |
|||
{ |
|||
return default!; |
|||
} |
|||
} |
|||
|
|||
public async Task StoreAsync(string prompt, GeneratedContent content, |
|||
CancellationToken ct) |
|||
{ |
|||
try |
|||
{ |
|||
// Use newtonsoft JSON because the CLI still deals with this library and uses JTokens.
|
|||
using var cacheStream = DefaultPools.MemoryStream.GetStream(); |
|||
#pragma warning disable MA0042 // Do not use blocking calls in an async method
|
|||
using var cacheWriter = new StreamWriter(cacheStream, Encoding.UTF8, leaveOpen: true); |
|||
|
|||
using (var jsonWriter = new JsonTextWriter(cacheWriter)) |
|||
{ |
|||
var serializer = new JsonSerializer(); |
|||
serializer.Serialize(jsonWriter, content); |
|||
jsonWriter.Flush(); |
|||
} |
|||
#pragma warning restore MA0042 // Do not use blocking calls in an async method
|
|||
|
|||
await distributedCache.SetAsync(CacheKey(prompt), cacheStream.ToArray(), cacheOptions, ct); |
|||
} |
|||
catch |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
private static string CacheKey(string prompt) |
|||
{ |
|||
return $"AI_{prompt.ToSha512()}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using ConsoleTables; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.AI.Implementation.OpenAI; |
|||
using Squidex.CLI.Commands.Implementation.AI; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure.Collections; |
|||
using Squidex.Infrastructure.Json; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Templates; |
|||
|
|||
public sealed class SchemaAIGenerator( |
|||
IQueryCache queryCache, |
|||
SessionFactory sessionFactory, |
|||
IOptions<SchemasOptions> schemasOptions, |
|||
IOptions<OpenAIChatOptions> openAIOptions) |
|||
{ |
|||
private const int MaxContentItems = 20; |
|||
|
|||
public async Task<SchemaAIResult> ExecuteAsync(App app, string prompt, int numberOfContentItems, bool execute, |
|||
CancellationToken ct) |
|||
{ |
|||
var apiKey = openAIOptions.Value.ApiKey; |
|||
if (string.IsNullOrWhiteSpace(apiKey)) |
|||
{ |
|||
throw new NotSupportedException("OpenAI ApiKey not configured."); |
|||
} |
|||
|
|||
var request = new GenerateRequest |
|||
{ |
|||
Description = prompt, |
|||
GenerateImages = false, |
|||
NumberOfAttempts = 3, |
|||
NumberOfContentItems = Math.Min(MaxContentItems, numberOfContentItems), |
|||
OpenAIApiKey = apiKey, |
|||
SystemPrompt = schemasOptions.Value.GeneratePrompt, |
|||
}; |
|||
|
|||
var generator = new AIContentGenerator(queryCache); |
|||
var generated = await generator.GenerateAsync(request, ct); |
|||
|
|||
using var cliLog = new StringLogger(); |
|||
WriteSchema(generated, cliLog); |
|||
WriteContent(generated, cliLog); |
|||
|
|||
if (execute) |
|||
{ |
|||
var session = sessionFactory.CreateSession(app); |
|||
|
|||
var executor = new AIContentExecutor(session, cliLog); |
|||
await executor.ExecuteAsync(request, generated, ct); |
|||
} |
|||
|
|||
return new SchemaAIResult(cliLog.Lines.ToReadonlyList(), execute ? generated.Schema.Name : null); |
|||
} |
|||
|
|||
private static void WriteSchema(GeneratedContent generated, StringLogger log) |
|||
{ |
|||
log.WriteLine($"Schema Name: {generated.Schema.Name}"); |
|||
log.WriteLine(); |
|||
log.WriteLine("Schema Fields:"); |
|||
|
|||
var schemaTable = new ConsoleTable("Name", "Type", "Required", "Localized"); |
|||
|
|||
schemaTable.Options.EnableCount = false; |
|||
foreach (var field in generated.Schema.Fields) |
|||
{ |
|||
schemaTable.AddRow(field.Name, field.Type, field.IsRequired, field.IsLocalized); |
|||
} |
|||
|
|||
log.WriteLine(schemaTable.ToString()); |
|||
} |
|||
|
|||
private static void WriteContent(GeneratedContent generated, StringLogger log) |
|||
{ |
|||
if (generated.Contents.Count > 0) |
|||
{ |
|||
const int MaximumPreview = 3; |
|||
|
|||
log.WriteLine(); |
|||
log.WriteLine("Contents:"); |
|||
log.WriteJson(generated.Contents.Take(MaximumPreview)); |
|||
|
|||
var more = generated.Contents.Count - MaximumPreview; |
|||
if (more > 0) |
|||
{ |
|||
log.WriteLine($"+ {more} content items"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Collections; |
|||
|
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Templates; |
|||
|
|||
public sealed record SchemaAIResult(ReadonlyList<string> Log, string? SchemaName); |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.CLI.Configuration; |
|||
using Squidex.ClientLibrary; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Templates; |
|||
|
|||
public sealed class SessionFactory(IOptions<TemplatesOptions> templateOptions, IUrlGenerator urlGenerator) |
|||
{ |
|||
private readonly TemplatesOptions options = templateOptions.Value; |
|||
|
|||
public Session CreateSession(App app) |
|||
{ |
|||
var client = app.Clients.First(); |
|||
|
|||
var url = options.LocalUrl; |
|||
if (string.IsNullOrEmpty(url)) |
|||
{ |
|||
url = urlGenerator.Root(); |
|||
} |
|||
|
|||
return new Session( |
|||
new DirectoryInfo(Path.GetTempPath()), |
|||
new SquidexClient(new SquidexOptions |
|||
{ |
|||
IgnoreSelfSignedCertificates = true, |
|||
AppName = app.Name, |
|||
ClientId = $"{app.Name}:{client.Key}", |
|||
ClientSecret = client.Value.Secret, |
|||
Url = url, |
|||
})); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Validation; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Schemas.Models; |
|||
|
|||
public sealed class GenerateSchemaDto |
|||
{ |
|||
/// <summary>
|
|||
/// The prompt to generate.
|
|||
/// </summary>
|
|||
[LocalizedRequired] |
|||
public string Prompt { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Indicates if the schema should actually be generated.
|
|||
/// </summary>
|
|||
public bool Execute { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The number of content items to generate.
|
|||
/// </summary>
|
|||
public int NumberOfContentItems { get; set; } |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Entities.Apps.Templates; |
|||
using Squidex.Infrastructure.Collections; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Schemas.Models; |
|||
|
|||
public sealed class GenerateSchemaResponseDto |
|||
{ |
|||
/// <summary>
|
|||
/// The status log.
|
|||
/// </summary>
|
|||
public ReadonlyList<string> Log { get; set; } = []; |
|||
|
|||
/// <summary>
|
|||
/// The name of the created schema.
|
|||
/// </summary>
|
|||
public string? SchemaName { get; set; } |
|||
|
|||
public static GenerateSchemaResponseDto FromDomain(SchemaAIResult response) |
|||
{ |
|||
return SimpleMapper.Map(response, new GenerateSchemaResponseDto()); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
You are a agent to create sample content for a headless CMS. |
|||
|
|||
When asked to create a **schema** for a content type, return a JSON document as the **first markdown code block**. Use the following format: |
|||
|
|||
``` |
|||
{ |
|||
"name": string, // The schema name in kebab-case, |
|||
"hint": string | null | undefined // Optional description of the schema, |
|||
"fields": [{ |
|||
"name": string, // Field name in camelCase, |
|||
"hint": string | null | undefined // Optional description of the field, |
|||
"type": "Slug | Text | MultilineText | Markdown | Number | Boolean", |
|||
"isRequired": false, |
|||
"isLocalized": false, |
|||
"minLength": number | null, // For Slug, Text, MultilineText, Markdown |
|||
"maxLength" number | null, // For Slug, Text, MultilineText, Markdown |
|||
"minValue": number | null, // For Number |
|||
"maxValue": number | null, // For Number |
|||
}] |
|||
} |
|||
``` |
|||
|
|||
### 🚫 Absolutely Forbidden Fields |
|||
|
|||
Do **not** include any **CMS metadata fields** under any circumstances. These fields are automatically managed by the CMS system and **not authored by content creators**. This includes fields related to: |
|||
|
|||
- **Publishing state** (e.g., `publishedDate`, `isPublished`, `published`) |
|||
- **Workflow state** (e.g., `isDraft`, `status`) |
|||
- **System timestamps** (e.g., `createdAt`, `updatedAt`) |
|||
- **Visibility** (e.g., `visibility`, `ready`) |
|||
|
|||
Here are some **examples** of prohibited fields: |
|||
|
|||
- `createdAt` |
|||
- `isDraft` |
|||
- `isReadyForPublish` |
|||
- `isPublished` |
|||
- `publishDate` |
|||
- `published` |
|||
- `publishedDate` |
|||
- `ready` |
|||
- `status` |
|||
- `updatedAt` |
|||
- `visibility` |
|||
|
|||
⚠️ **Note:** This list is *not exhaustive*. Any field related to **system-level behavior, versioning, publishing workflow, or timestamps** should **never** be included. |
|||
|
|||
Only include **fields that describe the actual content** of the schema. |
|||
|
|||
### Schema Rules: |
|||
|
|||
1. Use **kebab-case** for the schema name (e.g., my-schema). |
|||
2. Use **camelCase** for field names (e.g., myField). |
|||
3. Ensure that the schema **`hint`** accurately reflects the purpose of the schema, but keep it short and precise. Do not just repeat the fields. |
|||
4. Ensure that the field **hint** accurately reflects the purpose of the schema. It can be omitted if it does not provide useful or necessary information |
|||
|
|||
When asked to create **sample content**, return a second **markdown code block** with valid JSON—an **array of objects**. |
|||
|
|||
### Sample Content Rules: |
|||
|
|||
1. The result **must be valid JSON** representing an array of objects. |
|||
2. Each object's properties must match the **field names** in the schema. |
|||
3. Keep the **size of the sample content reasonable**. |
|||
4. For **localized fields**, use the following format (only use the language codes specified in the request): |
|||
|
|||
``` |
|||
{ |
|||
"languageCode": "Hello World" |
|||
} |
|||
``` |
|||
|
|||
5. For **image fields**, use this structure: |
|||
|
|||
``` |
|||
{ |
|||
"fileName": "Example.png" |
|||
"description": "Describe the image." |
|||
} |
|||
``` |
|||
|
|||
### 🛠 Correction Instructions: |
|||
|
|||
If you are asked to correct errors, **always return the full response**, including both schema and sample content (if requested). Do not omit the schema, even if it was already correct. |
|||
|
|||
@ -1,116 +1,169 @@ |
|||
<form [formGroup]="createForm.form" (ngSubmit)="createSchema()"> |
|||
<sqx-modal-dialog (dialogClose)="emitClose()" tourId="schemaForm"> |
|||
<form [formGroup]="actualForm.form" (ngSubmit)="createSchema()"> |
|||
<sqx-modal-dialog (dialogClose)="emitClose()" hasTabs="true" size="lg" tourId="schemaForm"> |
|||
<ng-container title> |
|||
@if (import) { |
|||
@if (source) { |
|||
{{ "schemas.clone" | sqxTranslate }} |
|||
} @else { |
|||
{{ "schemas.create" | sqxTranslate }} |
|||
} |
|||
</ng-container> |
|||
<ng-container content> |
|||
<sqx-form-error [error]="createForm.error | async" /> |
|||
<div class="form-group"> |
|||
<label for="name"> |
|||
{{ "common.name" | sqxTranslate }} <small class="hint">({{ "common.requiredHint" | sqxTranslate }})</small> |
|||
</label> |
|||
<sqx-control-errors for="name" /> |
|||
<input class="form-control" id="name" autocomplete="off" formControlName="name" sqxFocusOnInit sqxTransformInput="LowerCase" /> |
|||
<sqx-form-hint> {{ "schemas.schemaNameHint" | sqxTranslate }} </sqx-form-hint> |
|||
<ng-container tabs> |
|||
<div class="row align-items-center"> |
|||
<div class="col"> |
|||
<ul class="nav nav-tabs2"> |
|||
<li class="nav-item"> |
|||
<a class="nav-link" [class.active]="selectedTab === 0" (click)="selectTab(0)"> |
|||
{{ "schemas.createCustom" | sqxTranslate }} |
|||
</a> |
|||
</li> |
|||
|
|||
<li class="nav-item"> |
|||
<a class="nav-link" [class.active]="selectedTab === 1" (click)="selectTab(1)"> |
|||
{{ "schemas.createFromJson" | sqxTranslate }} |
|||
</a> |
|||
</li> |
|||
|
|||
@if (hasChatBot) { |
|||
<li class="nav-item"> |
|||
<a class="nav-link" [class.active]="selectedTab === 2" (click)="selectTab(2)"> |
|||
{{ "schemas.createAI" | sqxTranslate }} |
|||
<span class="badge rounded-pill badge-primary">New</span> |
|||
</a> |
|||
</li> |
|||
} |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
<ng-container content> |
|||
<sqx-form-error [error]="actualForm.error | async" /> |
|||
|
|||
@if (selectedTab !== 2) { |
|||
<div class="form-group"> |
|||
<label for="name"> |
|||
{{ "common.name" | sqxTranslate }} <small class="hint">({{ "common.requiredHint" | sqxTranslate }})</small> |
|||
</label> |
|||
<sqx-control-errors for="name" [submitCount]="createForm.submitCount | async" /> |
|||
<input class="form-control" id="name" autocomplete="off" formControlName="name" sqxFocusOnInit sqxTransformInput="LowerCase" /> |
|||
<sqx-form-hint> {{ "schemas.schemaNameHint" | sqxTranslate }} </sqx-form-hint> |
|||
</div> |
|||
|
|||
<sqx-form-alert> {{ "schemas.nameWarning" | sqxTranslate }} </sqx-form-alert> |
|||
} |
|||
|
|||
<div class="form-group mt-4"> |
|||
<div class="row"> |
|||
<div class="col-6 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Default'" /> |
|||
<div class="row g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Default'"> |
|||
<i class="icon-multiple-content"></i> |
|||
@if (selectedTab === 0) { |
|||
<div class="form-group mt-4"> |
|||
<div class="row"> |
|||
<div class="col-12 col-md-4 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Default'" /> |
|||
<div class="row flex-nowrap g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Default'"> |
|||
<i class="icon-multiple-content"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeMultiple" | sqxTranslate }}</div> |
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeMultiple" | sqxTranslate }}</div> |
|||
|
|||
<div class="type-text text-muted">{{ "schemas.modeMultipleDescription" | sqxTranslate }}</div> |
|||
<div class="type-text text-muted">{{ "schemas.modeMultipleDescription" | sqxTranslate }}</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</label> |
|||
</div> |
|||
</label> |
|||
</div> |
|||
|
|||
<div class="col-6 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Singleton'" /> |
|||
<div class="row g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Singleton'"> |
|||
<i class="icon-single-content"></i> |
|||
<div class="col-12 col-md-4 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Singleton'" /> |
|||
<div class="row flex-nowrap g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Singleton'"> |
|||
<i class="icon-single-content"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeSingle" | sqxTranslate }}</div> |
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeSingle" | sqxTranslate }}</div> |
|||
|
|||
<div class="type-text text-muted">{{ "schemas.modeSingleDescription" | sqxTranslate }}</div> |
|||
<div class="type-text text-muted">{{ "schemas.modeSingleDescription" | sqxTranslate }}</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</label> |
|||
</div> |
|||
</label> |
|||
</div> |
|||
|
|||
<div class="col-6 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Component'" /> |
|||
<div class="row g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Component'"> |
|||
<i class="icon-component"></i> |
|||
<div class="col-12 col-md-4 type"> |
|||
<label> |
|||
<input class="radio-input" formControlName="type" name="type" type="radio" [value]="'Component'" /> |
|||
<div class="row flex-nowrap g-0"> |
|||
<div class="col-auto"> |
|||
<div class="btn-radio" [class.active]="createForm.form.controls['type'].value === 'Component'"> |
|||
<i class="icon-component"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeComponent" | sqxTranslate }}</div> |
|||
<div class="col"> |
|||
<div class="type-title">{{ "schemas.modeComponent" | sqxTranslate }}</div> |
|||
|
|||
<div class="type-text text-muted">{{ "schemas.modeComponentDescription" | sqxTranslate }}</div> |
|||
<div class="type-text text-muted">{{ "schemas.modeComponentDescription" | sqxTranslate }}</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</label> |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<sqx-form-alert> {{ "schemas.nameWarning" | sqxTranslate }} </sqx-form-alert> |
|||
@if (schemasState.categoryNames | async; as categories) { |
|||
@if (categories.length > 0) { |
|||
<div class="form-group"> |
|||
<label for="category">{{ "common.category" | sqxTranslate }}</label> |
|||
<select class="form-select" id="category" formControlName="initialCategory"> |
|||
<option></option> |
|||
|
|||
@for (category of categories; track category) { |
|||
<option [ngValue]="category">{{ category }}</option> |
|||
} |
|||
</select> |
|||
</div> |
|||
} |
|||
} |
|||
|
|||
<div class="form-group"> |
|||
<button class="btn btn-sm btn-text-secondary" [class.hidden]="showImport" (click)="toggleImport()" type="button"> |
|||
{{ "schemas.import" | sqxTranslate }} |
|||
</button> |
|||
<button class="btn btn-sm btn-text-secondary force" [class.hidden]="!showImport" (click)="toggleImport()" type="button"> |
|||
{{ "common.hide" | sqxTranslate }} |
|||
</button> |
|||
@if (showImport) { |
|||
<sqx-code-editor formControlName="importing" [height]="250" valueMode="Json" /> |
|||
@if (schemasState.categoryNames | async; as categories) { |
|||
@if (categories.length > 0) { |
|||
<div class="form-group"> |
|||
<label for="category">{{ "common.category" | sqxTranslate }}</label> |
|||
<select class="form-select" id="category" formControlName="initialCategory"> |
|||
<option></option> |
|||
|
|||
@for (category of categories; track category) { |
|||
<option [ngValue]="category">{{ category }}</option> |
|||
} |
|||
</select> |
|||
</div> |
|||
} |
|||
} |
|||
</div> |
|||
} @else if (selectedTab === 1) { |
|||
<sqx-code-editor formControlName="importing" [height]="1000" valueMode="Json" /> |
|||
} @else if (selectedTab === 2) { |
|||
<div class="row g-2 form-group"> |
|||
<div class="col"> |
|||
<label for="prompt"> |
|||
{{ "common.prompt" | sqxTranslate }} <small class="hint">({{ "common.requiredHint" | sqxTranslate }})</small> |
|||
</label> |
|||
<sqx-control-errors for="prompt" /> |
|||
<input class="form-control" id="prompt" autocomplete="off" formControlName="prompt" sqxFocusOnInit /> |
|||
<sqx-form-hint> {{ "schemas.promptHint" | sqxTranslate }} </sqx-form-hint> |
|||
</div> |
|||
<div class="col-auto"> |
|||
<label> </label> |
|||
<div> |
|||
<button class="btn btn-primary" (click)="generatePreview()" [disabled]="generateForm.submitting | async" type="button"> |
|||
@if (generateForm.submitting | async) { |
|||
<sqx-loader color="white" size="12" /> |
|||
} |
|||
|
|||
{{ "common.generate" | sqxTranslate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<sqx-form-alert> {{ "schemas.promptExample" | sqxTranslate }} </sqx-form-alert> |
|||
|
|||
<sqx-code-editor disabled="true" [height]="1000" [ngModel]="generateLog" [ngModelOptions]="{ standalone: true }" /> |
|||
} |
|||
</ng-container> |
|||
<ng-container footer> |
|||
<button class="btn btn-text-secondary" (click)="dialogClose.emit()" type="button"> |
|||
{{ "common.cancel" | sqxTranslate }} |
|||
</button> |
|||
<button class="btn btn-success" type="submit">{{ "common.create" | sqxTranslate }}</button> |
|||
<button class="btn btn-success" [disabled]="selectedTab === 2 && !generateLog" type="submit"> |
|||
{{ "common.create" | sqxTranslate }} |
|||
</button> |
|||
</ng-container> |
|||
</sqx-modal-dialog> |
|||
</form> |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue