add sebis agent
This commit is contained in:
parent
f01016dab5
commit
921e1fbc0c
19
BotSharp.Core/Abstractions/IHookDbInitializer.cs
Normal file
19
BotSharp.Core/Abstractions/IHookDbInitializer.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using EntityFrameworkCore.BootKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize data for modules
|
||||
/// </summary>
|
||||
public interface IHookDbInitializer
|
||||
{
|
||||
/// <summary>
|
||||
/// value smaller is higher priority
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
void Load(Database dc);
|
||||
}
|
||||
}
|
||||
14
BotSharp.Core/Abstractions/IInitializationLoader.cs
Normal file
14
BotSharp.Core/Abstractions/IInitializationLoader.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Abstractions
|
||||
{
|
||||
public interface IInitializationLoader
|
||||
{
|
||||
int Priority { get; }
|
||||
void Initialize(IConfiguration config, IHostingEnvironment env);
|
||||
}
|
||||
}
|
||||
64
BotSharp.Core/Accounts/AccountCore.cs
Normal file
64
BotSharp.Core/Accounts/AccountCore.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using DotNetToolkit;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Accounts
|
||||
{
|
||||
public class AccountCore
|
||||
{
|
||||
private Database _dc;
|
||||
private IConfiguration _config;
|
||||
|
||||
public AccountCore(Database dc = null)
|
||||
{
|
||||
if (dc == null)
|
||||
{
|
||||
dc = new DefaultDataContextLoader().GetDefaultDc();
|
||||
}
|
||||
else
|
||||
{
|
||||
_dc = dc;
|
||||
}
|
||||
|
||||
_config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
}
|
||||
|
||||
public void CreateUser(User user)
|
||||
{
|
||||
user.Authenticaiton.IsActivated = false;
|
||||
user.Authenticaiton.Salt = PasswordHelper.GetSalt();
|
||||
user.Authenticaiton.Password = PasswordHelper.Hash(user.Authenticaiton.Password, user.Authenticaiton.Salt);
|
||||
user.Authenticaiton.ActivationCode = Guid.NewGuid().ToString("N");
|
||||
|
||||
_dc.Transaction<IDbRecord>(() =>
|
||||
{
|
||||
_dc.Table<User>().Add(user);
|
||||
});
|
||||
|
||||
|
||||
$"Created user {user.Email}, user id: {user.Id}".Log(LogLevel.INFO);
|
||||
}
|
||||
|
||||
public void Activate(string activationCode)
|
||||
{
|
||||
var activation = _dc.Table<UserAuth>().FirstOrDefault(x => x.ActivationCode == activationCode && !x.IsActivated);
|
||||
if (activation == null)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_dc.Transaction<IDbRecord>(() =>
|
||||
{
|
||||
activation = _dc.Table<UserAuth>().FirstOrDefault(x => x.ActivationCode == activationCode);
|
||||
activation.ActivationCode = String.Empty;
|
||||
activation.IsActivated = true;
|
||||
activation.UpdatedTime = DateTime.UtcNow;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
BotSharp.Core/Accounts/AccountDbInitializer.cs
Normal file
39
BotSharp.Core/Accounts/AccountDbInitializer.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using BotSharp.Core.Abstractions;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Accounts
|
||||
{
|
||||
public class AccountDbInitializer : IHookDbInitializer
|
||||
{
|
||||
public int Priority => 100;
|
||||
|
||||
public void Load(Database dc)
|
||||
{
|
||||
ImportAccount(dc);
|
||||
}
|
||||
|
||||
private void ImportAccount(Database dc)
|
||||
{
|
||||
var dataPath = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "DbInitializer", "Accounts");
|
||||
string json = File.ReadAllText(Path.Join(dataPath, "users.json"));
|
||||
|
||||
var users = JsonConvert.DeserializeObject<List<User>>(json);
|
||||
users.ForEach(user =>
|
||||
{
|
||||
if (!dc.Table<User>().Any(x => x.UserName == user.UserName))
|
||||
{
|
||||
var core = new AccountCore(dc);
|
||||
core.CreateUser(user);
|
||||
core.Activate(user.Authenticaiton.ActivationCode);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
60
BotSharp.Core/Accounts/User.cs
Normal file
60
BotSharp.Core/Accounts/User.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using EntityFrameworkCore.BootKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Accounts
|
||||
{
|
||||
/// <summary>
|
||||
/// User profile
|
||||
/// </summary>
|
||||
[Table("User")]
|
||||
public class User : DbRecord, IDbRecord
|
||||
{
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
public String UserName { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
[DataType(DataType.EmailAddress)]
|
||||
public String Email { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public String FirstName { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public String LastName { get; set; }
|
||||
|
||||
[MaxLength(256)]
|
||||
public String Description { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.DateTime)]
|
||||
public DateTime SignupDate { get; set; }
|
||||
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime? Birthday { get; set; }
|
||||
|
||||
[MaxLength(36)]
|
||||
public String Nationality { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public String FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
return FirstName + (String.IsNullOrEmpty(LastName) ? "" : " " + LastName);
|
||||
}
|
||||
}
|
||||
|
||||
public UserAuth Authenticaiton { get; set; }
|
||||
|
||||
public User()
|
||||
{
|
||||
SignupDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
BotSharp.Core/Accounts/UserAuth.cs
Normal file
36
BotSharp.Core/Accounts/UserAuth.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using EntityFrameworkCore.BootKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Accounts
|
||||
{
|
||||
/// <summary>
|
||||
/// User authentication
|
||||
/// </summary>
|
||||
[Table("UserAuth")]
|
||||
public class UserAuth : DbRecord, IDbRecord
|
||||
{
|
||||
[StringLength(36)]
|
||||
public String UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(256)]
|
||||
[DataType(DataType.Password)]
|
||||
public String Password { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(64)]
|
||||
public String Salt { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public String ActivationCode { get; set; }
|
||||
|
||||
public Boolean IsActivated { get; set; }
|
||||
|
||||
[ForeignKey("UserId")]
|
||||
public User User { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,11 +12,11 @@
|
|||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Haiping Chen</Authors>
|
||||
<Company />
|
||||
<Product>BotSharp Chabot Platform</Product>
|
||||
<Product>BotSharp Chatbot Platform</Product>
|
||||
<Description>Open source chatbot platform which is written in C# runs on .Net Core and is enterprise oriented. Integrated with multiple bot engines besides BotSharp bot engine. Modulized pipeline design make NLP tasks plugin easily. Abstract platform and NLP task, migrate existed chatbot from a platform into another platform perfectly through dump and restore.</Description>
|
||||
<RepositoryType>MIT</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/Oceania2018/BotSharp</RepositoryUrl>
|
||||
<PackageTags>NLU, Chatbot, Bot, AI Bot</PackageTags>
|
||||
<PackageTags>NLU, Chatbot, Bot, AI Bot, Artificial Intelligence, RPA</PackageTags>
|
||||
<Version>1.4.0</Version>
|
||||
<PackageReleaseNotes>Add NLP pipeline. Support training model by input context.</PackageReleaseNotes>
|
||||
<Copyright>Since 2018 Haiping Chen</Copyright>
|
||||
|
|
@ -36,7 +36,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetToolkit" Version="1.4.0" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="1.7.1" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="1.8.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="RestSharp" Version="106.3.1" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ namespace BotSharp.Core.Engines
|
|||
public BotEngineBase()
|
||||
{
|
||||
dc = new DefaultDataContextLoader().GetDefaultDc();
|
||||
DbInitializerPath = $"{Database.ContentRootPath}App_Data{Path.DirectorySeparatorChar}DbInitializer{Path.DirectorySeparatorChar}";
|
||||
string dataPath = AppDomain.CurrentDomain.GetData("DataPath").ToString();
|
||||
DbInitializerPath = Path.Join(dataPath, $"DbInitializer");
|
||||
}
|
||||
|
||||
public Agent LoadAgent(string id)
|
||||
|
|
@ -62,7 +63,7 @@ namespace BotSharp.Core.Engines
|
|||
{
|
||||
var importer = new TAgentImporter();
|
||||
|
||||
string dataDir = $"{DbInitializerPath}Agents{Path.DirectorySeparatorChar}";
|
||||
string dataDir = Path.Join(DbInitializerPath, "Agents");
|
||||
|
||||
int row = dc.DbTran(() => {
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using BotSharp.Core.Intents;
|
|||
using DotNetToolkit;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BotSharp.Core.Engines
|
||||
|
|
@ -43,22 +44,24 @@ namespace BotSharp.Core.Engines
|
|||
});
|
||||
|
||||
// Get NLP Provider
|
||||
string providerName = Database.Configuration.GetSection($"{config}:Provider").Value;
|
||||
var provider = TypeHelper.GetInstance(providerName, Database.Assemblies) as INlpPipeline;
|
||||
provider.Configuration = Database.Configuration.GetSection("BotSharpAi");
|
||||
var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies");
|
||||
string providerName = config.GetSection($"{config}:Provider").Value;
|
||||
var provider = TypeHelper.GetInstance(providerName, assemblies) as INlpPipeline;
|
||||
provider.Configuration = config.GetSection("BotSharpAi");
|
||||
provider.Process(agent, data);
|
||||
|
||||
//var corpus = agent.GrabCorpus(dc);
|
||||
|
||||
// pipe process
|
||||
var pipelines = Database.Configuration.GetSection($"{config}:Pipe").Value
|
||||
var pipelines = config.GetSection($"{config}:Pipe").Value
|
||||
.Split(',')
|
||||
.Select(x => x.Trim())
|
||||
.ToList();
|
||||
|
||||
pipelines.ForEach(pipeName =>
|
||||
{
|
||||
var pipe = TypeHelper.GetInstance(pipeName, Database.Assemblies) as INlpPipeline;
|
||||
var pipe = TypeHelper.GetInstance(pipeName, assemblies) as INlpPipeline;
|
||||
pipe.Configuration = provider.Configuration;
|
||||
pipe.Process(agent, data);
|
||||
});
|
||||
|
|
|
|||
33
BotSharp.Core/Engines/DbInitializer.cs
Normal file
33
BotSharp.Core/Engines/DbInitializer.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using BotSharp.Core.Abstractions;
|
||||
using DotNetToolkit;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines
|
||||
{
|
||||
public class DbInitializer : IInitializationLoader
|
||||
{
|
||||
public int Priority => 1;
|
||||
|
||||
public void Initialize(IConfiguration config, IHostingEnvironment env)
|
||||
{
|
||||
var dc = new DefaultDataContextLoader().GetDefaultDc();
|
||||
|
||||
var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies");
|
||||
var instances = TypeHelper.GetInstanceWithInterface<IHookDbInitializer>(assemblies);
|
||||
|
||||
// initial app db order by priority
|
||||
instances.OrderBy(x => x.Priority).ToList()
|
||||
.ForEach(instance =>
|
||||
{
|
||||
Console.WriteLine($"DbInitializer: {instance.ToString()}");
|
||||
dc.Transaction<IDbRecord>(() => instance.Load(dc));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ namespace BotSharp.Core.Engines
|
|||
public Agent LoadAgent(AgentImportHeader agentHeader, string agentDir)
|
||||
{
|
||||
// load agent profile
|
||||
string data = File.ReadAllText($"{agentDir}{Path.DirectorySeparatorChar}Dialogflow{Path.DirectorySeparatorChar}{agentHeader.Name}{Path.DirectorySeparatorChar}agent.json");
|
||||
string data = File.ReadAllText(Path.Join(agentDir, "Dialogflow", $"agentHeader.Name{Path.DirectorySeparatorChar}agent.json"));
|
||||
var agent = JsonConvert.DeserializeObject<DialogflowAgent>(data);
|
||||
agent.Name = agentHeader.Name;
|
||||
agent.Id = agentHeader.Id;
|
||||
|
|
@ -56,7 +56,7 @@ namespace BotSharp.Core.Engines
|
|||
public void LoadCustomEntities(Agent agent, string agentDir)
|
||||
{
|
||||
agent.Entities = new List<EntityType>();
|
||||
string entityDir = $"{agentDir}{Path.DirectorySeparatorChar}Dialogflow{Path.DirectorySeparatorChar}{agent.Name}{Path.DirectorySeparatorChar}entities";
|
||||
string entityDir = Path.Join(agentDir, "Dialogflow", $"{agent.Name}{Path.DirectorySeparatorChar}entities");
|
||||
if (!Directory.Exists(entityDir)) return;
|
||||
|
||||
Directory.EnumerateFiles(entityDir)
|
||||
|
|
@ -91,7 +91,7 @@ namespace BotSharp.Core.Engines
|
|||
public void LoadIntents(Agent agent, string agentDir)
|
||||
{
|
||||
agent.Intents = new List<Intent>();
|
||||
string intentDir = $"{agentDir}{Path.DirectorySeparatorChar}Dialogflow{Path.DirectorySeparatorChar}{agent.Name}{Path.DirectorySeparatorChar}intents";
|
||||
string intentDir = Path.Join(agentDir, "Dialogflow", $"{agent.Name}{Path.DirectorySeparatorChar}intents");
|
||||
if (!Directory.Exists(intentDir)) return;
|
||||
|
||||
Directory.EnumerateFiles(intentDir)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ namespace BotSharp.Core.Engines
|
|||
|
||||
private IRestResponse<RasaResponse> CallRasa(string projectId, string text, string model)
|
||||
{
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
var client = new RestClient($"{config.GetSection("Rasa:Nlu").Value}");
|
||||
|
||||
var rest = new RestRequest("parse", Method.POST);
|
||||
string json = JsonConvert.SerializeObject(new { Project = projectId, Q = text, Model = model },
|
||||
|
|
@ -105,7 +106,8 @@ namespace BotSharp.Core.Engines
|
|||
};
|
||||
|
||||
var corpus = GetIntentExpressions();
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
var client = new RestClient($"{config.GetSection("Rasa:Nlu").Value}");
|
||||
|
||||
var contextHashs = corpus.UserSays
|
||||
.Select(x => x.ContextHash)
|
||||
|
|
@ -195,7 +197,8 @@ namespace BotSharp.Core.Engines
|
|||
rest.AddQueryParameter("project", agent.Id);
|
||||
rest.AddQueryParameter("model", ctx);
|
||||
string trainingConfig = agent.Language == "zh" ? "config_jieba_mitie_sklearn.yml" : "config_mitie_sklearn.yml";
|
||||
string body = File.ReadAllText($"{Database.ContentRootPath}{Path.DirectorySeparatorChar}Settings{Path.DirectorySeparatorChar}{trainingConfig}");
|
||||
var contentRootPatch = AppDomain.CurrentDomain.GetData("ContentRootPath").ToString();
|
||||
string body = File.ReadAllText(Path.Join(contentRootPatch, "Settings", trainingConfig));
|
||||
body = $"{body}\r\ndata: {json}";
|
||||
rest.AddParameter("application/x-yml", body, ParameterType.RequestBody);
|
||||
|
||||
|
|
|
|||
162
BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs
Normal file
162
BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BotSharp.Core.Adapters.Dialogflow;
|
||||
using BotSharp.Core.Adapters.Sebis;
|
||||
using BotSharp.Core.Agents;
|
||||
using BotSharp.Core.Entities;
|
||||
using BotSharp.Core.Intents;
|
||||
using BotSharp.Core.Models;
|
||||
using DotNetToolkit;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BotSharp.Core.Engines
|
||||
{
|
||||
/// <summary>
|
||||
/// Import agent from Dialogflow
|
||||
/// </summary>
|
||||
public class AgentImporterInSebis : IAgentImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Load agent meta
|
||||
/// </summary>
|
||||
/// <param name="agentName"></param>
|
||||
/// <param name="agentDir"></param>
|
||||
/// <returns></returns>
|
||||
public Agent LoadAgent(AgentImportHeader agentHeader, string agentDir)
|
||||
{
|
||||
// load agent profile
|
||||
string data = File.ReadAllText(Path.Join(agentDir, "Sebis", $"{agentHeader.Name}{Path.DirectorySeparatorChar}agent.json"));
|
||||
var agent = JsonConvert.DeserializeObject<SebisAgent>(data);
|
||||
agent.Name = agentHeader.Name;
|
||||
agent.Id = agentHeader.Id;
|
||||
|
||||
var result = agent.ToObject<Agent>();
|
||||
result.ClientAccessToken = agentHeader.ClientAccessToken;
|
||||
result.DeveloperAccessToken = agentHeader.DeveloperAccessToken;
|
||||
if(agentHeader.UserId != null)
|
||||
{
|
||||
result.UserId = agentHeader.UserId;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void LoadCustomEntities(Agent agent, string agentDir)
|
||||
{
|
||||
agent.Entities = new List<EntityType>();
|
||||
}
|
||||
|
||||
public void LoadIntents(Agent agent, string agentDir)
|
||||
{
|
||||
string data = File.ReadAllText(Path.Join(agentDir, "Sebis", $"{agent.Name}{Path.DirectorySeparatorChar}corpus.json"));
|
||||
var sentences = JsonConvert.DeserializeObject<SebisAgent>(data).Sentences;
|
||||
|
||||
agent.Intents = sentences.Select(x => x.Name).Distinct().Select(x => new Intent{Name = x}).ToList();
|
||||
|
||||
agent.Intents.ForEach(intent => {
|
||||
intent.UserSays = new List<IntentExpression>();
|
||||
|
||||
var userSays = sentences.Where(x => x.Name == intent.Name).ToList();
|
||||
|
||||
userSays.ForEach(say =>
|
||||
{
|
||||
var expression = new IntentExpression();
|
||||
|
||||
expression.Data = new List<IntentExpressionPart>();
|
||||
|
||||
for(int index = 0; index < say.Entities.Count; index++)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
intent.UserSays.Add(expression);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Intent ImportIntentUserSays(Agent agent, Intent intent, string fileName)
|
||||
{
|
||||
// void id confict
|
||||
intent.Id = Guid.NewGuid().ToString();
|
||||
intent.Name = intent.Name.Replace("/", "_");
|
||||
// load user expressions
|
||||
|
||||
string expressionFileName = fileName.Replace(intent.Name, $"{intent.Name}_usersays_{agent.Language}");
|
||||
if (File.Exists(expressionFileName))
|
||||
{
|
||||
string expressionJson = File.ReadAllText($"{expressionFileName}");
|
||||
intent.UserSays = JsonConvert.DeserializeObject<List<IntentExpression>>(expressionJson);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadBuildinEntities(Agent agent, string agentDir)
|
||||
{
|
||||
agent.Intents.ForEach(intent =>
|
||||
{
|
||||
|
||||
if (intent.UserSays != null)
|
||||
{
|
||||
intent.UserSays.ForEach(us =>
|
||||
{
|
||||
us.Data.Where(data => data.Meta != null)
|
||||
.ToList()
|
||||
.ForEach(data =>
|
||||
{
|
||||
LoadBuildinEntityTypePerUserSay(agent, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadBuildinEntityTypePerUserSay(Agent agent, IntentExpressionPart data)
|
||||
{
|
||||
var existedEntityType = agent.Entities.FirstOrDefault(x => x.Name == data.Meta);
|
||||
|
||||
if (existedEntityType == null)
|
||||
{
|
||||
existedEntityType = new EntityType
|
||||
{
|
||||
Name = data.Meta,
|
||||
Entries = new List<EntityEntry>(),
|
||||
IsOverridable = true
|
||||
};
|
||||
|
||||
agent.Entities.Add(existedEntityType);
|
||||
}
|
||||
|
||||
var entries = existedEntityType.Entries.Select(x => x.Value.ToLower()).ToList();
|
||||
if (!entries.Contains(data.Text.ToLower()))
|
||||
{
|
||||
existedEntityType.Entries.Add(new EntityEntry
|
||||
{
|
||||
Value = data.Text,
|
||||
Synonyms = new List<EntrySynonym>
|
||||
{
|
||||
new EntrySynonym
|
||||
{
|
||||
Synonym = data.Text
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Sebis
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Lang { get; set; }
|
||||
public List<TrainingIntentExpression<TrainingIntentExpressionPart>> Sentences { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
20
BotSharp.Core/Engines/Sebis/SebisAgent.cs
Normal file
20
BotSharp.Core/Engines/Sebis/SebisAgent.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BotSharp.Core.Adapters.Sebis
|
||||
{
|
||||
public class SebisAgent
|
||||
{
|
||||
public String Id { get; set; }
|
||||
public String Name { get; set; }
|
||||
[JsonProperty("desc")]
|
||||
public String Description { get; set; }
|
||||
[JsonProperty("lang")]
|
||||
public String Language { get; set; }
|
||||
|
||||
public List<SebisIntent> Sentences { get; set; }
|
||||
}
|
||||
}
|
||||
16
BotSharp.Core/Engines/Sebis/SebisIntent.cs
Normal file
16
BotSharp.Core/Engines/Sebis/SebisIntent.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using BotSharp.Core.Intents;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Adapters.Sebis
|
||||
{
|
||||
public class SebisIntent
|
||||
{
|
||||
public string Text { get; set; }
|
||||
[JsonProperty("intent")]
|
||||
public string Name { get; set; }
|
||||
public List<SebisIntentExpressionPart> Entities { get; set; }
|
||||
}
|
||||
}
|
||||
12
BotSharp.Core/Engines/Sebis/SebisIntentExpression.cs
Normal file
12
BotSharp.Core/Engines/Sebis/SebisIntentExpression.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BotSharp.Core.Engines;
|
||||
|
||||
namespace BotSharp.Core.Adapters.Sebis
|
||||
{
|
||||
public class SebisIntentExpression : TrainingIntentExpression<SebisIntentExpressionPart>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
16
BotSharp.Core/Engines/Sebis/SebisIntentExpressionPart.cs
Normal file
16
BotSharp.Core/Engines/Sebis/SebisIntentExpressionPart.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BotSharp.Core.Engines;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BotSharp.Core.Adapters.Sebis
|
||||
{
|
||||
public class SebisIntentExpressionPart : TrainingIntentExpressionPart
|
||||
{
|
||||
[JsonProperty("stop")]
|
||||
public new int End { get; set; }
|
||||
[JsonProperty("text")]
|
||||
public new String Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using BotSharp.Core.Agents;
|
||||
using BotSharp.Core.Engines;
|
||||
using BotSharp.Core.Engines.BotSharp;
|
||||
using BotSharp.Core.Models;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -26,12 +27,12 @@ namespace BotSharp.RestApi
|
|||
[HttpGet("{agentId}")]
|
||||
public ActionResult Restore([FromRoute] String agentId)
|
||||
{
|
||||
var botsHeaderFilePath = $"{Database.ContentRootPath}App_Data{Path.DirectorySeparatorChar}DbInitializer{Path.DirectorySeparatorChar}Agents{Path.DirectorySeparatorChar}agents.json";
|
||||
var botsHeaderFilePath = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), $"DbInitializer{Path.DirectorySeparatorChar}Agents{Path.DirectorySeparatorChar}agents.json");
|
||||
var agents = JsonConvert.DeserializeObject<List<AgentImportHeader>>(System.IO.File.ReadAllText(botsHeaderFilePath));
|
||||
|
||||
var rasa = new RasaAi();
|
||||
var rasa = new BotSharpAi();
|
||||
var agentHeader = agents.First(x => x.Id == agentId);
|
||||
rasa.RestoreAgent<AgentImporterInDialogflow>(agentHeader);
|
||||
rasa.RestoreAgent<AgentImporterInSebis>(agentHeader);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
|
|||
63
BotSharp.RestApi/Authentication/AuthenticationController.cs
Normal file
63
BotSharp.RestApi/Authentication/AuthenticationController.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using BotSharp.Core.Accounts;
|
||||
using DotNetToolkit;
|
||||
using DotNetToolkit.JwtHelper;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.RestApi.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// User authentication
|
||||
/// </summary>
|
||||
public class AuthenticationController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Get user token
|
||||
/// </summary>
|
||||
/// <param name="userModel"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("/token")]
|
||||
public IActionResult Token([FromBody] VmUserLogin userModel)
|
||||
{
|
||||
if (String.IsNullOrEmpty(userModel.UserName) || String.IsNullOrEmpty(userModel.Password))
|
||||
{
|
||||
return new BadRequestObjectResult("Username and password should not be empty.");
|
||||
}
|
||||
|
||||
var dc = new DefaultDataContextLoader().GetDefaultDc();
|
||||
|
||||
// validate from local
|
||||
var user = (from usr in dc.Table<User>()
|
||||
join auth in dc.Table<UserAuth>() on usr.Id equals auth.UserId
|
||||
where usr.Email == userModel.UserName
|
||||
select auth).FirstOrDefault();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
// validate password
|
||||
string hash = PasswordHelper.Hash(userModel.Password, user.Salt);
|
||||
if (user.Password == hash)
|
||||
{
|
||||
var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
return Ok(JwtToken.GenerateToken(config, user.UserId));
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("Authorization Failed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("Account doesn't exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
BotSharp.RestApi/Authentication/VmUserLogin.cs
Normal file
22
BotSharp.RestApi/Authentication/VmUserLogin.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.RestApi.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// User login view model
|
||||
/// </summary>
|
||||
public class VmUserLogin
|
||||
{
|
||||
/// <summary>
|
||||
/// User identity, email or phone
|
||||
/// </summary>
|
||||
public String UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User password
|
||||
/// </summary>
|
||||
public String Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,19 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Haiping Chen</Authors>
|
||||
<Company>Personal</Company>
|
||||
<Product>BotSharp</Product>
|
||||
<Description>Restful API for BotSharp.Core</Description>
|
||||
<PackageProjectUrl>https://github.com/Oceania2018/BotSharp</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Oceania2018/BotSharp/tree/master/BotSharp.RestApi</RepositoryUrl>
|
||||
<RepositoryType>MIT</RepositoryType>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/Oceania2018/BotSharp/master/BotSharp.WebHost/wwwroot/images/BotSharp.png</PackageIconUrl>
|
||||
<Copyright>Since 2018 Haiping Chen</Copyright>
|
||||
<PackageLicenseUrl>https://github.com/Oceania2018/BotSharp/blob/master/LICENSE</PackageLicenseUrl>
|
||||
<PackageTags>NLU, Chatbot, Bot, AI Bot</PackageTags>
|
||||
<PackageReleaseNotes>Restful API for BotSharp.Core</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Voiceweb AI Chatbot Web Console
|
|||
## How to install
|
||||
#### Download source code
|
||||
````sh
|
||||
git clone https://github.com/voicecoin/voiceweb-chatbot-vue
|
||||
git clone https://github.com/Oceania2018/BotSharp
|
||||
````
|
||||
#### Run node.js server
|
||||
````sh
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import Env from './env';
|
|||
|
||||
let config = {
|
||||
env: Env,
|
||||
authURL: 'http://0.0.0.0:3112',
|
||||
baseURL: (Env == 'development' ? `http://0.0.0.0:3112` : `http://0.0.0.0:3112`),
|
||||
testAccount: {username: `botsharp@gmail.com`, password: (Env == 'development' ? `botsharp` : `botsharp`)}
|
||||
baseURL: (Env == 'development' ? `http://localhost:3112` : `http://localhost:3112`),
|
||||
testAccount: {username: `support@botsharp.io`, password: (Env == 'development' ? `botsharp` : ``)}
|
||||
};
|
||||
export default config;
|
||||
|
|
@ -88,7 +88,7 @@ const store = new Vuex.Store({
|
|||
authenticated(state, token) {
|
||||
localStorage.setItem('token', token);
|
||||
router.push('/agent/agents');
|
||||
HTTP.get('/account', { baseURL: config.authURL })
|
||||
HTTP.get('/account', { baseURL: config.baseURL })
|
||||
.then(response => {
|
||||
state.user = response.data;
|
||||
localStorage.setItem('user', JSON.stringify(response.data));
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
})
|
||||
},
|
||||
getToken() {
|
||||
this.$ajax.post(`/token`, this.formInline, { baseURL: this.$config.authURL })
|
||||
this.$ajax.post(`/token`, this.formInline, { baseURL: this.$config.baseURL })
|
||||
.then(response => {
|
||||
this.$store.commit('authenticated', response.data)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
},
|
||||
created() {
|
||||
let agentId = this.$route.query.agentId;
|
||||
this.$ajax.get(`/Account`, { baseURL: this.$config.authURL })
|
||||
this.$ajax.get(`/Account`, { baseURL: this.$config.baseURL })
|
||||
.then(response => {
|
||||
this.user = response.data;
|
||||
});
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
updateAgent(agentId){
|
||||
this.$ajax.put('/v1/Agents/' + agentId, this.agent)
|
||||
.then(response => {
|
||||
this.$Message.info("保存成功");
|
||||
this.$Message.info("Saved");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ namespace BotSharp.UnitTest
|
|||
[TestMethod]
|
||||
public void RestoreAgentFromDialogflowToRasaTest()
|
||||
{
|
||||
var botsHeaderFilePath = $"{Database.ContentRootPath}App_Data{Path.DirectorySeparatorChar}DbInitializer{Path.DirectorySeparatorChar}Agents{Path.DirectorySeparatorChar}agents.json";
|
||||
string dataPath = AppDomain.CurrentDomain.GetData("DataPath").ToString();
|
||||
var botsHeaderFilePath = Path.Join(dataPath, "DbInitializer", $"Agents{Path.DirectorySeparatorChar}agents.json");
|
||||
var agents = JsonConvert.DeserializeObject<List<AgentImportHeader>>(File.ReadAllText(botsHeaderFilePath));
|
||||
|
||||
agents.ForEach(agentHeader => {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ namespace BotSharp.UnitTest
|
|||
{
|
||||
configurationBuilder.AddJsonFile(setting, optional: false, reloadOnChange: true);
|
||||
});
|
||||
Database.Configuration = configurationBuilder.Build();
|
||||
|
||||
Database.Assemblies = new String[] { "BotSharp.Core" };
|
||||
Database.ContentRootPath = contentRoot;
|
||||
AppDomain.CurrentDomain.SetData("DataPath", Path.Join(contentRoot, "App_Data"));
|
||||
AppDomain.CurrentDomain.SetData("Configuration", configurationBuilder.Build());
|
||||
AppDomain.CurrentDomain.SetData("ContentRootPath", contentRoot);
|
||||
AppDomain.CurrentDomain.SetData("Assemblies", new String[] { "BotSharp.Core" });
|
||||
|
||||
dc = new DefaultDataContextLoader().GetDefaultDc();
|
||||
}
|
||||
|
|
|
|||
12
BotSharp.WebHost/App_Data/DbInitializer/Accounts/users.json
Normal file
12
BotSharp.WebHost/App_Data/DbInitializer/Accounts/users.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"userName": "support@botsharp.io",
|
||||
"email": "support@botsharp.io",
|
||||
"firstName": "Support",
|
||||
"lastName": "Botsharp",
|
||||
"description": "demo account",
|
||||
"authenticaiton": {
|
||||
"password": "botsharp"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"description": "NLU Evaluation Corpora",
|
||||
"language": "en"
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,5 +13,13 @@
|
|||
"AccessToken": "EAAJym8gGQFMBAPnsxTw6rZBE2WVfYtCJeGkCQ2NZC3VbQd45SyUlXjjgUf1gAkCOWq7v0blxTb1xLZAeMAXYhQj3btcMlMO9YbG7StMyx8ussz5TnD1tjjIZCye5u66PZAuFxSyIPXtTCin8GPiJ19cYB8ZCyH3rr5sro7OxUSyAZDZD"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "bff7605c-3db5-44dc-9ba7-1c9be2832318",
|
||||
"Name": "Airport",
|
||||
"UserId": "8da9e1e0-42dc-420a-8016-79b04c1297d0",
|
||||
"ClientAccessToken": "6ba8a06865944f14981ce18d229283f5",
|
||||
"DeveloperAccessToken": "f12fbdb0da5a4616b18fa7582d32f6e3",
|
||||
"Integrations": []
|
||||
}
|
||||
]
|
||||
26
BotSharp.WebHost/InitializationLoader.cs
Normal file
26
BotSharp.WebHost/InitializationLoader.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using BotSharp.Core.Abstractions;
|
||||
using DotNetToolkit;
|
||||
using EntityFrameworkCore.BootKit;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BotSharp.WebHost
|
||||
{
|
||||
public class InitializationLoader
|
||||
{
|
||||
public IHostingEnvironment Env { get; set; }
|
||||
public IConfiguration Config { get; set; }
|
||||
public void Load()
|
||||
{
|
||||
var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies");
|
||||
var appsLoaders1 = TypeHelper.GetInstanceWithInterface<IInitializationLoader>(assemblies);
|
||||
appsLoaders1.ForEach(loader => {
|
||||
loader.Initialize(Config, Env);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
9
BotSharp.WebHost/Settings/auth.json
Normal file
9
BotSharp.WebHost/Settings/auth.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"TokenAuthentication": {
|
||||
"SecretKey": "lfo54FYneUCJNL2EjP9ZxQ==",
|
||||
"Issuer": "BotSharp",
|
||||
"Audience": "BotSharp",
|
||||
"CookieName": "token",
|
||||
"Subject": "BotSharp"
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ using Microsoft.Extensions.PlatformAbstractions;
|
|||
using Newtonsoft.Json.Serialization;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using BotSharp.Core.Engines.BotSharp;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BotSharp.WebHost
|
||||
{
|
||||
|
|
@ -51,8 +53,10 @@ namespace BotSharp.WebHost
|
|||
// register platform dependency
|
||||
services.AddTransient<IBotPlatform>((provider) =>
|
||||
{
|
||||
var implements = TypeHelper.GetClassesWithInterface<IBotPlatform>(Database.Assemblies);
|
||||
string platform = Database.Configuration.GetValue<String>("BotPlatform");
|
||||
var assemblies = (String[])AppDomain.CurrentDomain.GetData("Assemblies");
|
||||
var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
|
||||
var implements = TypeHelper.GetClassesWithInterface<IBotPlatform>(assemblies);
|
||||
string platform = config.GetValue<String>("BotPlatform");
|
||||
var implement = implements.FirstOrDefault(x => x.Name.Split('.').Last() == platform);
|
||||
var instance = (IBotPlatform)Activator.CreateInstance(implement);
|
||||
|
||||
|
|
@ -101,15 +105,20 @@ namespace BotSharp.WebHost
|
|||
|
||||
app.UseMvc();
|
||||
|
||||
Database.Configuration = Configuration;
|
||||
Database.ContentRootPath = env.ContentRootPath;
|
||||
Database.Assemblies = Configuration.GetValue<String>("Assemblies").Split(',');
|
||||
|
||||
Runcmd();
|
||||
AppDomain.CurrentDomain.SetData("DataPath", Path.Join(env.ContentRootPath, "App_Data"));
|
||||
AppDomain.CurrentDomain.SetData("Configuration", Configuration);
|
||||
AppDomain.CurrentDomain.SetData("ContentRootPath", env.ContentRootPath);
|
||||
AppDomain.CurrentDomain.SetData("Assemblies", Configuration.GetValue<String>("Assemblies").Split(','));
|
||||
|
||||
InitializationLoader loader = new InitializationLoader();
|
||||
loader.Env = env;
|
||||
loader.Config = Configuration;
|
||||
loader.Load();
|
||||
|
||||
/*Runcmd();
|
||||
var ai = new BotSharpAi();
|
||||
ai.LoadAgent("6a9fd374-c43d-447a-97f2-f37540d0c725");
|
||||
ai.Train();
|
||||
ai.Train();*/
|
||||
}
|
||||
|
||||
public void Runcmd ()
|
||||
|
|
@ -152,4 +161,4 @@ namespace BotSharp.WebHost
|
|||
Console.WriteLine(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.RestApi", "BotShar
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.WebHost", "BotSharp.WebHost\BotSharp.WebHost.csproj", "{03DCA427-327A-4FC9-9A2F-57D17F16708C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MachineLearning", "BotSharp.MachineLearning\BotSharp.MachineLearning.csproj", "{E664115A-AE86-49E9-8AE4-D4589A568CD7}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.MachineLearning", "BotSharp.MachineLearning\BotSharp.MachineLearning.csproj", "{E664115A-AE86-49E9-8AE4-D4589A568CD7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
|
|||
Loading…
Reference in a new issue