Merge pull request #234 from gnh1201/dev

Update 2025-04-10
This commit is contained in:
Namhyeon Go 2025-04-10 02:29:10 +09:00 committed by GitHub
commit 426a9d7721
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 165 additions and 24 deletions

View File

@ -62,7 +62,7 @@ WelsonJS is tailored for developers who need a reliable, lightweight JavaScript
## Specifications ## Specifications
- Built-in transpilers: [TypeScript](https://www.typescriptlang.org/), [Rescript](https://rescript-lang.org/), [CoffeeScript 2](https://coffeescript.org/), [LiveScript](https://livescript.net/) - 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.** - **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) - 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) - 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) - Classical CSS Frameworks: [cascadeframework](https://github.com/jslegers/cascadeframework), [golden-layout](https://github.com/golden-layout/golden-layout)

View File

@ -242,10 +242,7 @@ namespace WelsonJS.Launcher
private void startCodeEditorToolStripMenuItem_Click(object sender, EventArgs e) private void startCodeEditorToolStripMenuItem_Click(object sender, EventArgs e)
{ {
if (Program.resourceServer == null) Program.StartResourceServer();
{
Program.resourceServer = new ResourceServer(Program.GetAppConfig("ResourceServerPrefix"), "editor.html");
}
if (!Program.resourceServer.IsRunning()) if (!Program.resourceServer.IsRunning())
{ {

View File

@ -138,13 +138,24 @@ namespace WelsonJS.Launcher
return workingDirectory; 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) public static void OpenWebBrowser(string url)
{ {
string userDataDir = Path.Combine(GetAppDataPath(), "EdgeUserProfile"); string userDataDir = Path.Combine(GetAppDataPath(), "EdgeUserProfile");
string remoteAllowOrigins = "http://localhost:3000"; string remoteAllowOrigins = GetAppConfig("ResourceServerPrefix");
int remoteDebuggingPort = new Uri(GetAppConfig("DevToolsPrefix")).Port;
string[] arguments = { string[] arguments = {
$"\"{url}\"", $"\"{url}\"",
"--remote-debugging-port=9222", $"--remote-debugging-port={remoteDebuggingPort}",
$"--remote-allow-origins={remoteAllowOrigins}", // for security reason $"--remote-allow-origins={remoteAllowOrigins}", // for security reason
$"--user-data-dir=\"{userDataDir}\"" $"--user-data-dir=\"{userDataDir}\""
}; };

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;

View File

@ -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<HttpResponseMessage> 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;
}
}
}

View File

@ -74,6 +74,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="IResourceTool.cs" /> <Compile Include="IResourceTool.cs" />
<Compile Include="ResourceTools\AzureAi.cs" />
<Compile Include="ResourceTools\Config.cs" /> <Compile Include="ResourceTools\Config.cs" />
<Compile Include="ResourceTools\Completion.cs" /> <Compile Include="ResourceTools\Completion.cs" />
<Compile Include="ResourceTools\DevTools.cs" /> <Compile Include="ResourceTools\DevTools.cs" />
@ -165,6 +166,5 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="editor.html" /> <EmbeddedResource Include="editor.html" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@ -50,16 +50,14 @@
<span class="icon mif-floppy-disks"></span> <span class="icon mif-floppy-disks"></span>
<span class="caption">Save File</span> <span class="caption">Save File</span>
</button> </button>
<button id="btnGenerate" class="ribbon-button"> <span class="title">File</span>
</div>
<div class="group">
<button id="btnCopilot" class="ribbon-button">
<span class="icon mif-rocket"></span> <span class="icon mif-rocket"></span>
<span class="caption">Generate</span> <span class="caption">Copilot</span>
</button> </button>
<button id="btnSponsor" class="ribbon-button"> <span class="title">Generative</span>
<span class="icon mif-heart"></span>
<span class="caption">Sponsor</span>
</button>
<span class="title">Common</span>
</div> </div>
<div class="group"> <div class="group">
<button id="btnWhois" class="ribbon-button"> <button id="btnWhois" class="ribbon-button">
@ -70,8 +68,7 @@
<span class="icon mif-earth"></span> <span class="icon mif-earth"></span>
<span class="caption">DNS</span> <span class="caption">DNS</span>
</button> </button>
<span class="title">Network</span>
<span class="title">Network tools</span>
</div> </div>
</div> </div>
</div> </div>
@ -240,18 +237,14 @@
document.body.removeChild(a); document.body.removeChild(a);
}; };
document.getElementById("btnSponsor").onclick = function () { document.getElementById("btnCopilot").onclick = function () {
navigate('https://github.com/sponsors/gnh1201');
};
document.getElementById("btnGenerate").onclick = function () {
const promptMessage = prompt("Enter a prompt message:", ''); const promptMessage = prompt("Enter a prompt message:", '');
if (!promptMessage || promptMessage.trim() == '') { if (!promptMessage || promptMessage.trim() == '') {
alert("A prompt message is required."); alert("A prompt message is required.");
return; return;
} }
appendTextToEditor(`\n//${promptMessage}... Thinking with Generative AI...`); appendTextToEditor(`\n//${promptMessage}... Generating text with Copilot...`);
(async function () { (async function () {
const targetWsUrl = await getTargetByUrl('copilot.microsoft.com'); const targetWsUrl = await getTargetByUrl('copilot.microsoft.com');