AI Voice zum Mok hinzugefügt
This commit is contained in:
@@ -146,7 +146,7 @@ namespace BeWoPlanerMobil.Controllers
|
||||
return;
|
||||
}
|
||||
|
||||
if(customerOid == -1)
|
||||
if(customerOid <= 0)
|
||||
{
|
||||
Model.CustomerSessionModel.ResetValues();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using DevExpress.XtraScheduler;
|
||||
using DevExpress.XtraScheduler.Compatibility;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
@@ -31,6 +32,7 @@ using FormCollection = System.Web.Mvc.FormCollection;
|
||||
using GoalRating = BeWoPlanerMobil.Util.GoalRating;
|
||||
using Image = System.Drawing.Image;
|
||||
using ServiceRecordDC = BS.Shared.DataContracts.ServiceRecordDC;
|
||||
using BeWo.Service.AI;
|
||||
|
||||
/*
|
||||
* ToDo: Bug, wenn man auf "bearbeiten" bei einem Eintrag klickt, wird die Leistung nicht geladen
|
||||
@@ -41,6 +43,13 @@ namespace BeWoPlanerMobil.Controllers
|
||||
{
|
||||
public class MainController : SignableController
|
||||
{
|
||||
// Speichert den Status externer Aufrufe (callId -> Callback-Daten)
|
||||
private static readonly ConcurrentDictionary<string, string> _externalCallResults = new ConcurrentDictionary<string, string>();
|
||||
// Speichert optionale Statusmeldungen für laufende externe Aufrufe (callId -> Statustext)
|
||||
private static readonly ConcurrentDictionary<string, string> _externalCallStatusMessages = new ConcurrentDictionary<string, string>();
|
||||
// Speichert das Aufnametoken für laufende externe Aufrufe (callId -> Token)
|
||||
private static readonly ConcurrentDictionary<string, AiVoiceDataDC> _externalCallTokens = new ConcurrentDictionary<string, AiVoiceDataDC>();
|
||||
|
||||
public MainController()
|
||||
{
|
||||
int test = 0;
|
||||
@@ -3557,6 +3566,131 @@ namespace BeWoPlanerMobil.Controllers
|
||||
return View("Main", Model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt die externe URL, die beim Klick auf den Test-Button aufgerufen werden soll.
|
||||
/// Die callbackUrl wird als Parameter mitgegeben, damit das externe System sie verwenden kann.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public JsonResult GetExternalCallUrl(string callId, string callbackUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var externalUrl = "";
|
||||
|
||||
if (Mandator?.OwnChatCode != null)
|
||||
{
|
||||
var ts = new TranscriptionService();
|
||||
var avd = ts.InitTranskription(MobileSessionFacade.Tenant, false);
|
||||
|
||||
externalUrl = avd.ResultUrl;
|
||||
|
||||
if (!string.IsNullOrEmpty(callId) && !string.IsNullOrEmpty(avd.Token))
|
||||
{
|
||||
_externalCallTokens[callId] = ts.MapAiVoiceDataDC(avd);
|
||||
}
|
||||
|
||||
//var externalBaseUrl = "https://example.com/external-service";
|
||||
//var externalUrl = externalBaseUrl
|
||||
// + "?callbackUrl=" + Uri.EscapeDataString(callbackUrl)
|
||||
// + "&callId=" + Uri.EscapeDataString(callId);
|
||||
}
|
||||
|
||||
|
||||
return Json(new { success = true, url = externalUrl }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
return Json(new { success = false, message = "Fehler beim Ermitteln der externen URL: " + ex.Message }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback-Endpunkt, der vom externen System aufgerufen wird.
|
||||
/// Markiert den externen Aufruf als abgeschlossen.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public ActionResult ExternalCallCallback(string callId, string data = null)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(callId))
|
||||
{
|
||||
_externalCallResults[callId] = data ?? string.Empty;
|
||||
_externalCallStatusMessages.TryRemove(callId, out _);
|
||||
_externalCallTokens.TryRemove(callId, out _);
|
||||
}
|
||||
|
||||
return Content("OK");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kann vom externen System aufgerufen werden, um eine Statusmeldung zu setzen,
|
||||
/// die dem Benutzer im Wartedialog angezeigt wird (ohne den Vorgang abzuschließen).
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public ActionResult ExternalCallUpdateStatus(string callId, string message)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(callId) && message != null)
|
||||
{
|
||||
_externalCallStatusMessages[callId] = message;
|
||||
}
|
||||
|
||||
return Content("OK");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wird vom Browser per Polling aufgerufen, um zu prüfen ob der Callback bereits eingegangen ist.
|
||||
/// Gibt bei completed=false optional eine Statusmeldung zurück.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public JsonResult CheckExternalCallStatus(string callId)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(callId) && _externalCallResults.TryRemove(callId, out var data))
|
||||
{
|
||||
_externalCallStatusMessages.TryRemove(callId, out _);
|
||||
_externalCallTokens.TryRemove(callId, out _);
|
||||
return Json(new { completed = true, data }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
string statusMessage = null;
|
||||
if(!string.IsNullOrEmpty(callId) && _externalCallTokens.TryRemove(callId, out var avddc))
|
||||
{
|
||||
var ts = new TranscriptionService();
|
||||
|
||||
var avd = ts.MapAiVoiceData(avddc);
|
||||
|
||||
avd = ts.GetTranskription(avd);
|
||||
statusMessage = avd.StatusMessage;
|
||||
avddc = ts.MapAiVoiceDataDC(avd);
|
||||
|
||||
_externalCallTokens[callId] = avddc;
|
||||
|
||||
//_externalCallStatusMessages.TryGetValue(callId, out statusMessage);
|
||||
}
|
||||
|
||||
return Json(new { completed = false, statusMessage }, JsonRequestBehavior.AllowGet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wird aufgerufen, wenn der Benutzer den externen Aufruf abbricht.
|
||||
/// Räumt den Eintrag auf, falls vorhanden.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public ActionResult CancelExternalCall(string callId)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(callId))
|
||||
{
|
||||
_externalCallResults.TryRemove(callId, out _);
|
||||
_externalCallStatusMessages.TryRemove(callId, out _);
|
||||
_externalCallTokens.TryRemove(callId, out _);
|
||||
}
|
||||
|
||||
return new HttpStatusCodeResult(200);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public ActionResult ResetGroupBookingForm()
|
||||
|
||||
@@ -58,11 +58,61 @@ namespace BeWoPlanerMobil.Models
|
||||
|
||||
public bool HasRightToInsertRessourceAppointments => CheckRight(UserRightType.KalenderRessourcentermineAnlegen) || CheckRight(UserRightType.CreateAll);
|
||||
|
||||
public bool HasRightToViewQuittierungsbelege
|
||||
public bool HasRightToUseAiVoice
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
var showAiVoice = MobileUtils.GetSettingValue(Mandator.Settings, SettingsKeys.ShowAIVoice);
|
||||
var showOwnChat = MobileUtils.GetSettingValue(Mandator.Settings, SettingsKeys.ShowChat);
|
||||
|
||||
#if DEBUG
|
||||
showAiVoice = "1";
|
||||
showOwnChat = "1";
|
||||
|
||||
//tenant = "4368658435";
|
||||
//var serverUrl = OwnChatHelper.ErmittleServerURL("1", tenant);
|
||||
//var apikey = "hNykpBwxE1Y6NmwZBTDQOpPXW6xCgnVHjJ5lh0QDQyCefgcnimC32aZ3CwB4qQl4";
|
||||
//var userid = "1432790061";
|
||||
|
||||
Mandator.OwnChatCode = "HcYki-8hLbQ";
|
||||
|
||||
return true;
|
||||
#endif
|
||||
if ("1".Equals(showAiVoice) && CheckRight(UserRightType.AiVoiceView))
|
||||
{
|
||||
if (LoggedInEmployee != null && Mandator.OwnChatCode == null)
|
||||
{
|
||||
Mandator.OwnChatCode = "";
|
||||
|
||||
if ("1".Equals(showOwnChat))
|
||||
{
|
||||
var es = new BeWo.Service.ServiceImplementations.EmployeeServiceImp();
|
||||
var empAppCodes = es.GetAllEmployeeAppCodes(LoggedInEmployee.EmployeeOid.Value);
|
||||
|
||||
if (empAppCodes != null && empAppCodes.Count > 0)
|
||||
{
|
||||
foreach (var item in empAppCodes)
|
||||
{
|
||||
if (item.EmployeeCode != null && !item.EmployeeCode.Equals(""))
|
||||
Mandator.OwnChatCode = item.EmployeeCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return !String.IsNullOrEmpty(Mandator.OwnChatCode);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRightToViewQuittierungsbelege
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
var showEmployeeSignatureSetting = MobileUtils.GetSettingValue(Mandator.Settings, SettingsKeys.ShowSignature);
|
||||
#if DEBUG
|
||||
showEmployeeSignatureSetting = "1";
|
||||
@@ -72,7 +122,6 @@ namespace BeWoPlanerMobil.Models
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool IsAllowedToSeeSupportConceptFilter
|
||||
{
|
||||
get
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace BeWoPlanerMobil.Models
|
||||
//public string Website { get; set; }
|
||||
//public string PostalCode { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public string OwnChatCode { get; set; }
|
||||
//public string State { get; set; }
|
||||
//public string Street { get; set; }
|
||||
//public string Town { get; set; }
|
||||
|
||||
@@ -92,6 +92,7 @@ function changeGroupBookingFormEnableState(enableState) {
|
||||
"#gb-distance-input, " +
|
||||
"textarea, " +
|
||||
"#reset-button, " +
|
||||
"#aivoice-button, " +
|
||||
"#create-button, " +
|
||||
"#gb-textbausteine-btn, " +
|
||||
"#gb-hours-minutes-dropdown-btn, " +
|
||||
@@ -116,6 +117,7 @@ function changeSingleBookingFormEnableState(enableState) {
|
||||
"#goals-button, " +
|
||||
"textarea, " +
|
||||
"#reset-button, " +
|
||||
"#aivoice-button, " +
|
||||
"#create-button, " +
|
||||
"#statistics-button, " +
|
||||
"#timeFrameSelect, " +
|
||||
|
||||
@@ -168,6 +168,94 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var _externalCallPollingTimer = null;
|
||||
var _externalCallId = null;
|
||||
|
||||
function startExternalCall() {
|
||||
// Eindeutige ID für diesen Aufruf generieren
|
||||
_externalCallId = 'ext-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
|
||||
// Callback-URL für das externe System
|
||||
var callbackUrl = window.location.origin + '@Url.Action("ExternalCallCallback", "Main")' + '?callId=' + encodeURIComponent(_externalCallId);
|
||||
|
||||
// Externe URL vom Server ermitteln lassen
|
||||
$.ajax({
|
||||
url: '@Url.Action("GetExternalCallUrl", "Main")',
|
||||
data: { callId: _externalCallId, callbackUrl: callbackUrl },
|
||||
type: 'GET',
|
||||
success: function(result) {
|
||||
if (result && result.success && result.url) {
|
||||
// Overlay anzeigen und Statustext zurücksetzen
|
||||
$('#external-call-status-text').text('Um die Aufnahme zu starten, klicken Sie auf den Link, den wir an Ihren ownChat Account gesendet haben.');
|
||||
document.getElementById('external-call-overlay').style.display = 'flex';
|
||||
|
||||
// Externe URL in neuem Tab/Fenster öffnen
|
||||
window.open(result.url, '_blank');
|
||||
|
||||
// Polling starten - prüft regelmäßig ob der Callback aufgerufen wurde
|
||||
_externalCallPollingTimer = setInterval(function() {
|
||||
$.ajax({
|
||||
url: '@Url.Action("CheckExternalCallStatus", "Main")',
|
||||
data: { callId: _externalCallId },
|
||||
type: 'GET',
|
||||
success: function(pollResult) {
|
||||
if (pollResult && pollResult.completed) {
|
||||
stopExternalCallPolling();
|
||||
onExternalCallCompleted(pollResult.data);
|
||||
} else if (pollResult && pollResult.statusMessage) {
|
||||
$('#external-call-status-text').text(pollResult.statusMessage);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
// Bei Fehler weiter pollen
|
||||
}
|
||||
});
|
||||
}, 3000);
|
||||
} else {
|
||||
// Fehler beim Ermitteln der URL
|
||||
var errorMsg = (result && result.message) ? result.message : 'Die externe URL konnte nicht ermittelt werden.';
|
||||
showAlertMessageBox(errorMsg);
|
||||
_externalCallId = null;
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
showAlertMessageBox('Fehler bei der Kommunikation mit dem Server.');
|
||||
_externalCallId = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelExternalCall() {
|
||||
// callId merken BEVOR stopExternalCallPolling() sie auf null setzt
|
||||
var callIdToCancel = _externalCallId;
|
||||
stopExternalCallPolling();
|
||||
// Server informieren, dass abgebrochen wurde
|
||||
if (callIdToCancel) {
|
||||
$.ajax({
|
||||
url: '@Url.Action("CancelExternalCall", "Main")',
|
||||
data: { callId: callIdToCancel },
|
||||
type: 'POST'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stopExternalCallPolling() {
|
||||
if (_externalCallPollingTimer) {
|
||||
clearInterval(_externalCallPollingTimer);
|
||||
_externalCallPollingTimer = null;
|
||||
}
|
||||
_externalCallId = null;
|
||||
document.getElementById('external-call-overlay').style.display = 'none';
|
||||
}
|
||||
|
||||
function onExternalCallCompleted(data) {
|
||||
// Hier kann die Rückmeldung vom externen System verarbeitet werden
|
||||
// z.B. Daten in ein Formularfeld schreiben
|
||||
console.log('Externer Aufruf abgeschlossen. Daten:', data);
|
||||
}
|
||||
</script>
|
||||
|
||||
@using(Html.BeginForm("ResetSingleBookingForm", "Main", FormMethod.Post, new { Id = "reset-single-booking-form-form" })) { }
|
||||
|
||||
<div class="row">
|
||||
@@ -580,29 +668,47 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="w-100 mt-2">
|
||||
<button type="button" class="btn btn-primary float-right ml-1" id="create-button" onclick="submitSingleBookingForm()">@Model.SubmitButtonValue</button>
|
||||
<div class="w-100 mt-2">
|
||||
<button type="button" class="btn btn-primary float-right ml-1" id="create-button" onclick="submitSingleBookingForm()">@Model.SubmitButtonValue</button>
|
||||
|
||||
@if(Model.IsInEditingMode)
|
||||
@if (Model.IsInEditingMode)
|
||||
{
|
||||
using (Html.BeginForm("ResetEditingMode", "Main", FormMethod.Post))
|
||||
{
|
||||
using(Html.BeginForm("ResetEditingMode", "Main", FormMethod.Post))
|
||||
{
|
||||
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
|
||||
}
|
||||
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
|
||||
}
|
||||
else if(Model.IsInTransferMode)
|
||||
}
|
||||
else if (Model.IsInTransferMode)
|
||||
{
|
||||
using (Html.BeginForm("ResetTransferMode", "Main", FormMethod.Post))
|
||||
{
|
||||
using(Html.BeginForm("ResetTransferMode", "Main", FormMethod.Post))
|
||||
{
|
||||
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
|
||||
}
|
||||
<button type="submit" class="btn btn-primary float-right" onclick="showSpinner()">Abbrechen</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary float-right" id="reset-button" onclick="resetSingleBookingForm();">Abbrechen</button>
|
||||
}
|
||||
|
||||
@Html.Partial("RestoreServiceRecordInputsPartial", Model)
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary float-right" id="reset-button" onclick="resetSingleBookingForm();">Abbrechen</button>
|
||||
}
|
||||
|
||||
@if (Model.HasRightToUseAiVoice)
|
||||
{
|
||||
<button type="button" class="btn btn-primary float-right mr-1" id="aivoice-button" onclick="startExternalCall();">AI Voice</button>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Html.Partial("RestoreServiceRecordInputsPartial", Model)
|
||||
</div>
|
||||
|
||||
@* Waiting-Overlay für externen Aufruf *@
|
||||
<div id="external-call-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:9999; justify-content:center; align-items:center;">
|
||||
<div style="background:white; border-radius:8px; padding:30px; text-align:center; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
||||
<div class="mb-3">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"></i>
|
||||
</div>
|
||||
<p class="mb-3" id="external-call-status-text">Warte auf Rückmeldung vom externen System...</p>
|
||||
<button type="button" class="btn btn-danger" onclick="cancelExternalCall();">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<script type="text/javascript" src="@Scripts.Url("~/Scripts/moment/locale/de.js")"></script>
|
||||
<script type="text/javascript" src="@Scripts.Url("~/Scripts/ownSoft-Scripts/mobileUtils.js?v=1.9")"></script>
|
||||
|
||||
<script type="text/javascript" src="@Scripts.Url("~/Scripts/ownSoft-Scripts/view-scripts/main.js?v=2.6")"></script>
|
||||
<script type="text/javascript" src="@Scripts.Url("~/Scripts/ownSoft-Scripts/view-scripts/main.js?v=2.7")"></script>
|
||||
<script type="text/javascript" src="@Scripts.Url("~/Scripts/ownSoft-Scripts/signature.js?v=1.5")"></script>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="@Url.Content("~/Scripts/bootstrap/bootstrap4-modal-fullscreen.min.css")" />
|
||||
|
||||
Reference in New Issue
Block a user