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