mirror of https://github.com/Squidex/squidex.git
Browse Source
* Select client. * Fixes to OpenAPI: Parameter order. * Fix scripting. * Fix content type. * Save all files. * Fix tests * Improve errors. * Fix schema name. * Fix exception type. * Fix versions. * Fix script. * Chatbot. * Added missing files. * Added a loading spinner. * Fix migration * Revert change. * Fix login flow. * Fix home page. * Fix GraphQL. * Add schema name.pull/1003/head
committed by
GitHub
74 changed files with 1235 additions and 626 deletions
@ -0,0 +1,114 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime; |
||||
|
using Squidex.Domain.Apps.Core.Properties; |
||||
|
using Squidex.Text.ChatBots; |
||||
|
using Squidex.Text.Translations; |
||||
|
|
||||
|
#pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.Extensions; |
||||
|
|
||||
|
public sealed class StringAsyncJintExtension : IJintExtension, IScriptDescriptor |
||||
|
{ |
||||
|
private delegate void TextGenerateDelegate(string prompt, Action<JsValue> callback); |
||||
|
private delegate void TextTranslateDelegate(string text, string language, Action<JsValue> callback, string sourceLanguage); |
||||
|
private readonly ITranslator translator; |
||||
|
private readonly IChatBot chatBot; |
||||
|
|
||||
|
public StringAsyncJintExtension(ITranslator translator, IChatBot chatBot) |
||||
|
{ |
||||
|
this.translator = translator; |
||||
|
this.chatBot = chatBot; |
||||
|
} |
||||
|
|
||||
|
public void ExtendAsync(ScriptExecutionContext context) |
||||
|
{ |
||||
|
var generate = new TextGenerateDelegate((prompt, callback) => |
||||
|
{ |
||||
|
Generate(context, prompt, callback); |
||||
|
}); |
||||
|
|
||||
|
var translate = new TextTranslateDelegate((text, language, callback, sourceLanguage) => |
||||
|
{ |
||||
|
Translate(context, text, language, callback, sourceLanguage); |
||||
|
}); |
||||
|
|
||||
|
context.Engine.SetValue("generate", generate); |
||||
|
context.Engine.SetValue("translate", translate); |
||||
|
} |
||||
|
|
||||
|
private void Generate(ScriptExecutionContext context, string prompt, Action<JsValue> callback) |
||||
|
{ |
||||
|
if (callback == null) |
||||
|
{ |
||||
|
throw new JavaScriptException("Callback is not defined."); |
||||
|
} |
||||
|
|
||||
|
context.Schedule(async (scheduler, ct) => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(prompt)) |
||||
|
{ |
||||
|
scheduler.Run(callback, JsValue.Null); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var choices = await chatBot.AskQuestionAsync(prompt, ct); |
||||
|
|
||||
|
scheduler.Run(callback, JsValue.FromObject(context.Engine, choices.FirstOrDefault())); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
throw new JavaScriptException(ex.Message); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void Translate(ScriptExecutionContext context, string text, string language, Action<JsValue> callback, string sourceLanguage) |
||||
|
{ |
||||
|
if (callback == null) |
||||
|
{ |
||||
|
throw new JavaScriptException("Callback is not defined."); |
||||
|
} |
||||
|
|
||||
|
context.Schedule(async (scheduler, ct) => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(language)) |
||||
|
{ |
||||
|
scheduler.Run(callback, JsValue.Null); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var translation = await translator.TranslateAsync(text, language, sourceLanguage, ct); |
||||
|
|
||||
|
scheduler.Run(callback, JsValue.FromObject(context.Engine, translation.Text)); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
throw new JavaScriptException(ex.Message); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void Describe(AddDescription describe, ScriptScope scope) |
||||
|
{ |
||||
|
if (scope.HasFlag(ScriptScope.Async)) |
||||
|
{ |
||||
|
describe(JsonType.Function, "generate(prompt, callback?", |
||||
|
Resources.ScriptingGenerate); |
||||
|
|
||||
|
describe(JsonType.Function, "translate(text, language, callback, sourceLanguage?", |
||||
|
Resources.ScriptingTranslate); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
using Squidex.Web; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Translations.Models; |
||||
|
|
||||
|
[OpenApiRequest] |
||||
|
public sealed class AskDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The text to ask.
|
||||
|
/// </summary>
|
||||
|
[LocalizedRequired] |
||||
|
public string Prompt { get; set; } |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
<sqx-modal-dialog size="lg" (close)="close.emit()"> |
||||
|
<ng-container title> |
||||
|
{{ 'chat.title' | sqxTranslate }} |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container content> |
||||
|
<sqx-form-alert [marginBottom]="4"> |
||||
|
<span [sqxMarkdown]="'chat.description' | sqxTranslate" [inline]="true"></span> |
||||
|
</sqx-form-alert> |
||||
|
|
||||
|
<form (ngSubmit)="ask()"> |
||||
|
<div class="row row-cols-0 g-2"> |
||||
|
<div class="col"> |
||||
|
<input class="form-control" placeholder="{{ 'chat.prompt' | sqxTranslate }}" |
||||
|
sqxFocusOnInit |
||||
|
(ngModelChange)="setQuestion($event)" |
||||
|
[ngModel]="snapshot.chatQuestion" |
||||
|
[ngModelOptions]="{ standalone: true }" |
||||
|
[disabled]="snapshot.isRunning" /> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<button type="submit" class="btn btn-secondary" [disabled]="snapshot.isRunning"> |
||||
|
{{ 'chat.ask' | sqxTranslate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
|
||||
|
<ng-container *ngIf="!snapshot.isRunning; else loading"> |
||||
|
<ng-container *ngIf="snapshot.chatAnswers"> |
||||
|
<h4 class="mt-4">{{ 'chat.answers' | sqxTranslate }}</h4> |
||||
|
|
||||
|
<div *ngIf="snapshot.chatAnswers?.length === 0"> |
||||
|
{{ 'chat.noAnswers' | sqxTranslate }} |
||||
|
</div> |
||||
|
|
||||
|
<div class="row g-2 answer" *ngFor="let answer of (snapshot.chatAnswers || [])"> |
||||
|
<div class="col"> |
||||
|
<textarea class="form-control" readonly [ngModel]="answer"></textarea> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<button type="submit" class="btn btn-primary" (click)="complete.emit(answer)"> |
||||
|
{{ 'chat.use' | sqxTranslate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #loading> |
||||
|
<div class="mt-4"> |
||||
|
<sqx-loader></sqx-loader> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</ng-container> |
||||
|
</sqx-modal-dialog> |
||||
@ -0,0 +1,6 @@ |
|||||
|
@import 'mixins'; |
||||
|
@import 'vars'; |
||||
|
|
||||
|
textarea { |
||||
|
height: 100px; |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectorRef, Component, EventEmitter, Output } from '@angular/core'; |
||||
|
import { AppsState, StatefulComponent, TranslationsService } from '@app/shared'; |
||||
|
|
||||
|
interface State { |
||||
|
// True, when running
|
||||
|
isRunning: boolean; |
||||
|
|
||||
|
// The questions.
|
||||
|
chatQuestion: string; |
||||
|
|
||||
|
// The answers.
|
||||
|
chatAnswers?: ReadonlyArray<string>; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-chat-dialog', |
||||
|
styleUrls: ['./chat-dialog.component.scss'], |
||||
|
templateUrl: './chat-dialog.component.html', |
||||
|
}) |
||||
|
export class ChatDialogComponent extends StatefulComponent<State> { |
||||
|
@Output() |
||||
|
public close = new EventEmitter(); |
||||
|
|
||||
|
@Output() |
||||
|
public complete = new EventEmitter<string>(); |
||||
|
|
||||
|
constructor(changeDetector: ChangeDetectorRef, |
||||
|
private readonly appsState: AppsState, |
||||
|
private readonly translator: TranslationsService, |
||||
|
) { |
||||
|
super(changeDetector, { |
||||
|
isRunning: false, |
||||
|
chatQuestion: '', |
||||
|
chatAnswers: undefined, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public setQuestion(chatQuestion: string) { |
||||
|
this.next({ chatQuestion }); |
||||
|
} |
||||
|
|
||||
|
public ask() { |
||||
|
this.next({ isRunning: true }); |
||||
|
|
||||
|
this.translator.ask(this.appsState.appName, { prompt: this.snapshot.chatQuestion }) |
||||
|
.subscribe({ |
||||
|
next: chatAnswers => { |
||||
|
this.next({ chatAnswers }); |
||||
|
}, |
||||
|
error: () => { |
||||
|
this.next({ chatAnswers: [] }); |
||||
|
}, |
||||
|
complete: () => { |
||||
|
this.next({ isRunning: false }); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue