diff --git a/README.md b/README.md index a329e7b..645f791 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ WelsonJS is tailored for developers who need a reliable, lightweight JavaScript ## Specifications - Built-in transpilers: [TypeScript](https://www.typescriptlang.org/), [Rescript](https://rescript-lang.org/), [CoffeeScript 2](https://coffeescript.org/), [LiveScript](https://livescript.net/) - **Ready to use on Windows machine immediately. No require additional software installation.** -- **WelsonJS Launcher**: Manage an instances (Like a container), User-defined variable editor, [Microsoft Monaco Editor](https://github.com/microsoft/monaco-editor) (Pre-embedded rich code editor), [Microsoft Copilot](https://copilot.microsoft.com) on the code editor. +- **WelsonJS Launcher**: Manage instances (Like a container), User-defined variable editor, [Microsoft Monaco Editor](https://github.com/microsoft/monaco-editor) (Pre-embedded rich code editor), [Microsoft Copilot](https://copilot.microsoft.com) on the code editor. - ES5(ECMAScript 5), XML, JSON, YAML compatibility: [core-js](https://github.com/zloirock/core-js), [JSON2.js](https://github.com/douglascrockford/JSON-js), [js-yaml](https://github.com/nodeca/js-yaml) - HTML5, CSS3 compatibility: [html5shiv](https://github.com/aFarkas/html5shiv), [jquery-html5-placeholder-shim](https://github.com/parndt/jquery-html5-placeholder-shim), [Respond](https://github.com/scottjehl/Respond), [selectivizr](https://github.com/keithclark/selectivizr), [ExplorerCanvas](https://github.com/arv/ExplorerCanvas), [Modernizr](https://github.com/Modernizr/Modernizr) - Classical CSS Frameworks: [cascadeframework](https://github.com/jslegers/cascadeframework), [golden-layout](https://github.com/golden-layout/golden-layout) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index 11e21e5..dcb3ce2 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -242,10 +242,7 @@ namespace WelsonJS.Launcher private void startCodeEditorToolStripMenuItem_Click(object sender, EventArgs e) { - if (Program.resourceServer == null) - { - Program.resourceServer = new ResourceServer(Program.GetAppConfig("ResourceServerPrefix"), "editor.html"); - } + Program.StartResourceServer(); if (!Program.resourceServer.IsRunning()) { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index aebad66..cb51fca 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -138,13 +138,24 @@ namespace WelsonJS.Launcher return workingDirectory; } + public static void StartResourceServer() + { + lock(typeof(Program)) + { + if (resourceServer == null) + { + resourceServer = new ResourceServer(GetAppConfig("ResourceServerPrefix"), "editor.html"); + } + } + } public static void OpenWebBrowser(string url) { string userDataDir = Path.Combine(GetAppDataPath(), "EdgeUserProfile"); - string remoteAllowOrigins = "http://localhost:3000"; + string remoteAllowOrigins = GetAppConfig("ResourceServerPrefix"); + int remoteDebuggingPort = new Uri(GetAppConfig("DevToolsPrefix")).Port; string[] arguments = { $"\"{url}\"", - "--remote-debugging-port=9222", + $"--remote-debugging-port={remoteDebuggingPort}", $"--remote-allow-origins={remoteAllowOrigins}", // for security reason $"--user-data-dir=\"{userDataDir}\"" }; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 02f3e0b..c218177 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Net; using System.Net.Http; using System.Reflection; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs new file mode 100644 index 0000000..147e78a --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher.ResourceTools +{ + public class AzureAi : IResourceTool + { + private ResourceServer Server; + private const string Prefix = "azure-ai/"; + private const int ChunkSize = 4096; + private readonly string AzureAiServiceUrl; + private readonly string AzureAiServiceApiKey; + + public AzureAi(ResourceServer server) + { + Server = server; + + AzureAiServiceUrl = Program.GetAppConfig("AzureAiServiceUrl"); + AzureAiServiceApiKey = Program.GetAppConfig("AzureAiServiceApiKey"); + } + + public bool CanHandle(string path) + { + return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + string apiKey = AzureAiServiceApiKey; + if (string.IsNullOrEmpty(apiKey)) + { + WriteError(context, "Missing the API key.", HttpStatusCode.BadRequest); + return; + } + + string requestBody = ReadRequestBody(context); + bool isStreaming = ContainsSequentialKeywords(requestBody, new[] { "\"stream\"", "true" }, 30); + + try + { + using (var response = await SendAzureRequestAsync(apiKey, requestBody, isStreaming)) + { + await ForwardResponseAsync(context, response, isStreaming); + } + } + catch (HttpRequestException ex) + { + WriteError(context, "Request error: " + ex.Message, HttpStatusCode.BadGateway); + } + } + + private string ReadRequestBody(HttpListenerContext context) + { + using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) + { + return reader.ReadToEnd(); + } + } + + private async Task SendAzureRequestAsync(string apiKey, string requestBody, bool isStreaming) + { + using (var client = new HttpClient()) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Post, AzureAiServiceUrl); + requestMessage.Headers.Add("api-key", apiKey); + requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + + var completionOption = isStreaming + ? HttpCompletionOption.ResponseHeadersRead + : HttpCompletionOption.ResponseContentRead; + + return await client.SendAsync(requestMessage, completionOption); + } + } + + private async Task ForwardResponseAsync(HttpListenerContext context, HttpResponseMessage response, bool isStreaming) + { + context.Response.StatusCode = (int)response.StatusCode; + context.Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/json"; + + using (var responseStream = await response.Content.ReadAsStreamAsync()) + { + if (isStreaming) + { + context.Response.SendChunked = true; + context.Response.Headers.Add("Transfer-Encoding", "chunked"); + context.Response.Headers.Add("Cache-Control", "no-cache"); + + byte[] buffer = new byte[ChunkSize]; + int bytesRead; + while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await context.Response.OutputStream.WriteAsync(buffer, 0, bytesRead); + context.Response.OutputStream.Flush(); + } + } + else + { + await responseStream.CopyToAsync(context.Response.OutputStream); + } + } + } + + private void WriteError(HttpListenerContext context, string message, HttpStatusCode statusCode) + { + context.Response.StatusCode = (int)statusCode; + using (var writer = new StreamWriter(context.Response.OutputStream, Encoding.UTF8)) + { + writer.Write(message); + } + } + + public static bool ContainsSequentialKeywords(string input, string[] keywords, int maxDistance) + { + if (input == null || keywords == null || keywords.Length < 2) + return false; + + int position = 0; + + for (int i = 0; i < keywords.Length; i++) + { + int index = input.IndexOf(keywords[i], position, StringComparison.OrdinalIgnoreCase); + if (index < 0) + return false; + + if (i > 0 && (index - position) > maxDistance) + return false; + + position = index + keywords[i].Length; + } + + return true; + } + } +} \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index df1cc5c..731acee 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -74,6 +74,7 @@ + @@ -165,6 +166,5 @@ - \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html index 53a1b50..3e49309 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html @@ -50,16 +50,14 @@ Save File - - - - Common + Generative
- - Network tools + Network
@@ -240,18 +237,14 @@ document.body.removeChild(a); }; - document.getElementById("btnSponsor").onclick = function () { - navigate('https://github.com/sponsors/gnh1201'); - }; - - document.getElementById("btnGenerate").onclick = function () { + document.getElementById("btnCopilot").onclick = function () { const promptMessage = prompt("Enter a prompt message:", ''); if (!promptMessage || promptMessage.trim() == '') { alert("A prompt message is required."); return; } - appendTextToEditor(`\n//${promptMessage}... Thinking with Generative AI...`); + appendTextToEditor(`\n//${promptMessage}... Generating text with Copilot...`); (async function () { const targetWsUrl = await getTargetByUrl('copilot.microsoft.com');