Merge pull request #239 from gnh1201/dev

Improve WelsonJS Editor with React framework
This commit is contained in:
Namhyeon Go 2025-04-18 19:51:56 +09:00 committed by GitHub
commit 2225ab9011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 565 additions and 442 deletions

View File

@ -90,9 +90,18 @@ namespace WelsonJS.Launcher.Properties {
/// <summary>
/// https://catswords.blob.core.windows.net/welsonjs/과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string BlobServerPrefix {
internal static string BlobStoragePrefix {
get {
return ResourceManager.GetString("BlobServerPrefix", resourceCulture);
return ResourceManager.GetString("BlobStoragePrefix", resourceCulture);
}
}
/// <summary>
/// https://cdnjs.cloudflare.com/과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string CdnJsPrefix {
get {
return ResourceManager.GetString("CdnJsPrefix", resourceCulture);
}
}

View File

@ -178,10 +178,13 @@
<data name="WhoisServerUrl" xml:space="preserve">
<value>https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois.jsc</value>
</data>
<data name="BlobServerPrefix" xml:space="preserve">
<data name="BlobStoragePrefix" xml:space="preserve">
<value>https://catswords.blob.core.windows.net/welsonjs/</value>
</data>
<data name="AzureAiServiceApiVersion" xml:space="preserve">
<value>2024-05-01-preview</value>
</data>
<data name="CdnJsPrefix" xml:space="preserve">
<value>https://cdnjs.cloudflare.com/</value>
</data>
</root>

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Reflection;
@ -23,7 +22,6 @@ namespace WelsonJS.Launcher
private string _prefix;
private string _resourceName;
private List<IResourceTool> _tools = new List<IResourceTool>();
private const int _httpClientTimeout = 5000;
private readonly HttpClient _httpClient = new HttpClient();
private readonly string _defaultMimeType = "application/octet-stream";
@ -33,7 +31,7 @@ namespace WelsonJS.Launcher
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
_resourceName = resourceName;
_httpClient.Timeout = TimeSpan.FromMilliseconds(_httpClientTimeout);
_httpClient.Timeout = TimeSpan.FromSeconds(30);
// Add resource tools
_tools.Add(new ResourceTools.Completion(this, _httpClient));
@ -129,56 +127,76 @@ namespace WelsonJS.Launcher
ServeResource(context, GetResource(_resourceName), "text/html");
}
private async Task<bool> ServeBlob(HttpListenerContext context, string path)
private async Task<bool> ServeBlob(HttpListenerContext context, string path, string prefix = null)
{
byte[] data;
string mimeType;
// Try serve data from the cached blob
if (TryGetCachedBlob(path, out mimeType, true))
if (!String.IsNullOrEmpty(prefix))
{
if (TryGetCachedBlob(path, out data))
{
if (String.IsNullOrEmpty(mimeType))
{
mimeType = _defaultMimeType;
}
string url = $"{prefix}{path}";
ServeResource(context, data, mimeType);
try
{
using (var client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd(context.Request.UserAgent);
HttpResponseMessage response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
Trace.TraceError($"Failed to serve blob. URL: {url}, Status: {response.StatusCode}");
return false;
}
data = await response.Content.ReadAsByteArrayAsync();
mimeType = response.Content.Headers.ContentType?.MediaType ?? _defaultMimeType;
ServeResource(context, data, mimeType);
_ = TrySaveCachedBlob(path, data, mimeType);
return true;
}
}
catch (Exception ex)
{
Trace.TraceError($"Failed to serve blob. URL: {url}, Exception: {ex.Message}");
return false;
}
}
else
{
// use the cached data
if (TryGetCachedBlob(path, out mimeType, true))
{
if (TryGetCachedBlob(path, out data))
{
if (String.IsNullOrEmpty(mimeType))
{
mimeType = _defaultMimeType;
}
ServeResource(context, data, mimeType);
return true;
}
}
// use the cdn-js
if (path.StartsWith("ajax/libs/"))
{
if (await ServeBlob(context, path, Program.GetAppConfig("CdnJsPrefix"))) {
return true;
}
}
// use the blob stroage
if (await ServeBlob(context, path, Program.GetAppConfig("BlobStoragePrefix"))) {
return true;
}
}
// If not cached yet
try
{
string blobServerPrefix = Program.GetAppConfig("BlobServerPrefix");
string url = $"{blobServerPrefix}{path}";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd(context.Request.UserAgent);
HttpResponseMessage response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
Trace.TraceError($"Failed to serve blob. Status: {response.StatusCode}");
return false;
}
data = await response.Content.ReadAsByteArrayAsync();
mimeType = response.Content.Headers.ContentType?.MediaType ?? _defaultMimeType;
ServeResource(context, data, mimeType);
_ = TrySaveCachedBlob(path, data, mimeType);
return true;
}
catch (Exception ex)
{
Trace.TraceError($"Failed to serve blob. Exception: {ex.Message}");
return false;
}
return false;
}
private string GetCachedBlobPath(string path)
@ -220,7 +238,7 @@ namespace WelsonJS.Launcher
}
catch (Exception ex)
{
Trace.TraceError($"Error: {ex.Message}");
Trace.TraceError($"Cache Read Error: {ex.Message}");
}
data = null;

View File

@ -12,7 +12,8 @@
<add key="WhoisReferrerUrl" value="https://xn--c79as89aj0e29b77z.xn--3e0b707e/kor/whois/whois.jsp"/>
<add key="WhoisClientAddress" value="141.101.82.1"/>
<add key="DnsServerAddress" value="1.1.1.1"/>
<add key="BlobServerPrefix" value="https://catswords.blob.core.windows.net/welsonjs/"/>
<add key="BlobStoragePrefix" value="https://catswords.blob.core.windows.net/welsonjs/"/>
<add key="CdnJsPrefix" value="https://cdnjs.cloudflare.com/"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>

View File

@ -3,11 +3,8 @@
<head>
<title>WelsonJS Editor</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link rel="stylesheet" href="http://localhost:3000/ajax/libs/metroui/dev/lib/metro.css" integrity="sha384-4XgOiXH2ZMaWt5s5B35yKi7EAOabhZvx7wO8Jr71q2vZ+uONdRza/6CsK2kpyocd" crossorigin="anonymous">
<link rel="stylesheet" href="http://localhost:3000/ajax/libs/metroui/dev/lib/icons.css" integrity="sha384-FuLND994etg+RtnpPSPMyNBvL+fEz+xGhbN61WUWuDEeZ+wJzcQ8SGqAMuI5hWrt" crossorigin="anonymous">
<link rel="stylesheet" href="http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.css" integrity="sha384-06yHXpYRlHEPaR4AS0fB/W+lMN09Zh5e1XMtfkNQdHV38OlhfkOEW5M+pCj3QskC" crossorigin="anonymous">
<style>
html, body {
html, body, #app, #app > .app {
margin: 0;
padding: 0;
width: 100%;
@ -17,12 +14,17 @@
#container {
border: 1px solid grey;
height: calc(100% - 139px);
height: calc(100% - 167px);
display: flex;
overflow: hidden;
}
#fileInput {
display: none;
#editor {
flex: 3;
}
#promptEditor {
flex: 1;
}
.banner {
@ -34,63 +36,7 @@
</style>
</head>
<body>
<nav data-role="ribbonmenu">
<ul class="tabs-holder">
<li class="static"><a href="#">WelsonJS</a></li>
<li><a href="#editor-tab">Editor</a></li>
</ul>
<div class="content-holder">
<div class="section" id="editor-tab">
<div class="group">
<button id="btnOpenFile" class="ribbon-button">
<span class="icon mif-folder-open"></span>
<span class="caption">Open File</span>
</button>
<button id="btnSaveFile" class="ribbon-button">
<span class="icon mif-floppy-disks"></span>
<span class="caption">Save File</span>
</button>
<span class="title">File</span>
</div>
<div class="group">
<button id="btnCopilot" class="ribbon-button">
<span class="icon mif-rocket"></span>
<span class="caption">Copilot</span>
</button>
<button id="btnAzureAi" class="ribbon-button">
<span class="icon mif-rocket"></span>
<span class="caption">Azure AI</span>
</button>
<button id="btnSavePrompt" class="ribbon-button">
<span class="icon mif-floppy-disks"></span>
<span class="caption">Save</span>
</button>
<button id="btnLoadPrompt" class="ribbon-button">
<span class="icon mif-file-upload"></span>
<span class="caption">Load</span>
</button>
<span class="title">Generative AI</span>
</div>
<div class="group">
<button id="btnWhois" class="ribbon-button">
<span class="icon mif-earth"></span>
<span class="caption">Whois</span>
</button>
<button id="btnDnsQuery" class="ribbon-button">
<span class="icon mif-earth"></span>
<span class="caption">DNS</span>
</button>
<span class="title">Network tools</span>
</div>
</div>
</div>
</nav>
<div id="container"></div>
<input type="file" id="fileInput">
<div class="banner"><a href="https://github.com/gnh1201/welsonjs">WelsonJS</a> Code Editor powered by <a href="https://github.com/microsoft/monaco-editor">Microsoft Monaco Editor</a>.</div>
<div id="app"></div>
<script>
var require = {
paths: {
@ -98,372 +44,518 @@
}
};
</script>
<script src="http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.js" integrity="sha384-NdVVc20Tze856ZAWEoJNCk0mL4zJrGztRwULc5Hz25HUXQQgYtmjFtgVAxR4p5dD" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/axios/1.8.4/axios.min.js" integrity="sha384-06w+raHvkSL3+E7mbQ2X6DZwI5A3veU8Ba+NLrAPxxRGw4Xy78sihHDHQMustMM4" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/fast-xml-parser/4.5.1/fxparser.min.js" integrity="sha384-ae/HepOQ8hiJ/VA6yGwPMGXQXOkT/lJpjlcQ7EUgibUcfnBltuozgNj4IgOZ9QLc" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/dompurify/3.2.4/purify.min.js" integrity="sha384-eEu5CTj3qGvu9PdJuS+YlkNi7d2XxQROAFYOr59zgObtlcux1ae1Il3u7jvdCSWu" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/metroui/dev/lib/metro.js" integrity="sha384-grz4KlnFmdCd5ELenGIdPkUL/l+44UC4SniSke/OZQyRYXaQ1EDlGigacn6z4hGB" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/loader.js" integrity="sha384-pHG02SG8pId94Np3AbPmBEJ1yPqaH0IkJGLSNGXYmuGhkazT8Lr/57WYpbkGjJtu" crossorigin="anonymous"></script>
<script src="http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.js" integrity="sha384-fj9z+NUc93I3woCCy5IRQfrQ8Amu1E27tllwgb5gz3d9Vr1ymS13xcF6two3e4KH" crossorigin="anonymous"></script>
<script>
let editor;
let currentFileName = "sayhello.js";
const serverPrefix = "http://localhost:3000/";
let settings = {};
let promptMessages = [];
function loadResource(url, mimeType, integrity) {
mimeType = mimeType || 'application/javascript';
function fetchSettings() {
axios.get(`${serverPrefix}settings`)
.then(response => {
const parser = new XMLParser();
const result = parser.parse(response.data);
settings = result.settings;
});
return new Promise((resolve, reject) => {
let el;
if (mimeType === 'text/css') {
el = document.createElement('link');
el.rel = 'stylesheet';
el.type = mimeType;
el.href = url;
} else {
el = document.createElement('script');
el.type = mimeType;
el.src = url;
}
if (integrity) {
el.integrity = integrity;
el.crossOrigin = 'anonymous';
}
el.onload = resolve;
el.onerror = reject;
(mimeType === 'text/css' ? document.head : document.body).appendChild(el);
});
}
function resizeEditor() {
if (editor) {
const ribbonHeight = document.querySelector('nav').offsetHeight;
const bannerHeight = document.querySelector('.banner').offsetHeight;
const containerHeight = document.documentElement.clientHeight - ribbonHeight - bannerHeight;
document.getElementById('container').style.height = containerHeight + 'px';
editor.layout();
Promise.all([
loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/metro.css", "text/css", "sha384-4XgOiXH2ZMaWt5s5B35yKi7EAOabhZvx7wO8Jr71q2vZ+uONdRza/6CsK2kpyocd"),
loadResource("http://localhost:3000/ajax/libs/metroui/dev/lib/icons.css", "text/css", "sha384-FuLND994etg+RtnpPSPMyNBvL+fEz+xGhbN61WUWuDEeZ+wJzcQ8SGqAMuI5hWrt"),
loadResource("http://localhost:3000/ajax/libs/monaco-editor/0.52.2/min/vs/editor/editor.main.css", "text/css", "sha384-06yHXpYRlHEPaR4AS0fB/W+lMN09Zh5e1XMtfkNQdHV38OlhfkOEW5M+pCj3QskC"),
loadResource("http://localhost:3000/ajax/libs/jsoneditor/10.1.3/jsoneditor.min.css", "text/css", "sha384-cj1rYBc4/dVYAknZMTkVCDRL6Knzugf32igVqsuFW0iRWFHKH8Ci8+ekC8gNsFZ+")
]).then(() => {
const _e = React.createElement;
function Button({ id, icon, caption, onClick }) {
return _e(
'button',
{ id, className: 'ribbon-button', onClick },
_e('span', { className: `icon ${icon}` }),
_e('span', { className: 'caption' }, caption)
);
}
}
function getSuggestions(word, range) {
return axios.get(`${serverPrefix}completion/${encodeURIComponent(word)}`)
.then(response => {
const parser = new XMLParser();
const result = parser.parse(response.data);
function Group({ title, buttons }) {
return _e(
'div',
{ className: 'group' },
buttons.map((btn, index) =>
_e(Button, { key: index, ...btn })
),
_e('span', { className: 'title' }, title)
);
}
if (!result.suggestions || !result.suggestions.item) {
function RibbonMenu({
onOpenFileClick, onSaveFileClick, onCopliotClick, onAzureAiClick,
onSavePromptClick, onLoadPromptClick, onQueryWhoisClick, onQueryDnsClick
}) {
const fileButtons = [
{
id: 'btnOpenFile',
icon: 'mif-folder-open',
caption: 'Open File',
onClick: onOpenFileClick
},
{
id: 'btnSaveFile',
icon: 'mif-floppy-disks',
caption: 'Save File',
onClick: onSaveFileClick
}
];
const aiButtons = [
{ id: 'btnCopilot', icon: 'mif-rocket', caption: 'Copilot', onClick: onCopliotClick },
{ id: 'btnAzureAi', icon: 'mif-rocket', caption: 'Azure AI', onClick: onAzureAiClick },
{ id: 'btnSavePrompt', icon: 'mif-floppy-disks', caption: 'Save', onClick: onSavePromptClick },
{ id: 'btnLoadPrompt', icon: 'mif-file-upload', caption: 'Load', onClick: onLoadPromptClick }
];
const networkToolsButtons = [
{ id: 'btnWhois', icon: 'mif-earth', caption: 'Whois', onClick: onQueryWhoisClick },
{ id: 'btnDnsQuery', icon: 'mif-earth', caption: 'DNS', onClick: onQueryDnsClick }
];
return _e(
'nav',
{ 'className': 'ribbon-menu' },
_e('ul', { className: 'tabs-holder' },
_e('li', { className: 'static' }, _e('a', { href: '#heading-tab' }, 'WelsonJS')),
_e('li', { className: 'active' }, _e('a', { href: '#editor-tab' }, 'Editor'))
),
_e('div', { className: 'content-holder' },
_e('div', { className: 'section active', id: 'editor-tab' },
_e(Group, { title: 'File', buttons: fileButtons }),
_e(Group, { title: 'Generative AI', buttons: aiButtons }),
_e(Group, { title: 'Network tools', buttons: networkToolsButtons })
)
)
);
}
function Editor({ editorRef }) {
const containerRef = React.useRef(null);
React.useEffect(() => {
if (!containerRef.current)
return;
require(['vs/editor/editor.main'], () => {
const instance = monaco.editor.create(containerRef.current, {
value: ['// lib/sayhello.js', 'function say() {', ' console.log("hello");', '}', '', 'exports.say = say;', '', 'exports.VERSIONINFO = "SayHello (sayhello.js) version 0.1";', 'exports.AUTHOR = "abuse@catswords.net";', 'exports.global = global;', 'exports.require = global.require;'].join('\n'),
language: 'javascript'
});
editorRef.current = instance;
});
}, []);
return _e('div', { id: 'editor', ref: containerRef });
}
function PromptEditor({ promptEditorRef, promptMessagesRef }) {
const containerRef = React.useRef(null);
React.useEffect(() => {
if (!containerRef.current)
return;
const options = {};
const instance = new JSONEditor(containerRef.current, options);
instance.set(promptMessagesRef.current);
promptEditorRef.current = instance;
}, []);
return _e('div', { id: 'promptEditor', ref: containerRef });
}
function App() {
const serverPrefix = "http://localhost:3000/";
const editorRef = React.useRef(null);
const promptEditorRef = React.useRef(null);
const settingsRef = React.useRef({});
const fileNameRef = React.useRef('sayhello.js');
const promptMessagesRef = React.useRef([]);
const fetchSettings = () => axios.get(`${serverPrefix}settings`)
.then(response => {
const parser = new XMLParser();
const result = parser.parse(response.data);
settingsRef.current = result.settings;
});
const resizeEditor = () => {
if (editorRef.current) {
const ribbon = document.querySelector('nav')?.offsetHeight || 0;
const banner = document.querySelector('.banner')?.offsetHeight || 0;
const h = document.documentElement.clientHeight - ribbon - banner;
const editorDiv = document.getElementById('editor');
if (editorDiv) editorDiv.style.height = h + 'px';
if (editorRef.current) editorRef.current.layout();
}
};
const getSuggestions = (word) => axios.get(`${serverPrefix}completion/${encodeURIComponent(word)}`)
.then(response => {
const parser = new XMLParser();
const result = parser.parse(response.data);
if (!result.suggestions || !result.suggestions.item) {
return {
suggestions: []
};
}
const items = Array.isArray(result.suggestions.item) ? result.suggestions.item : [result.suggestions.item];
const suggestions = items.map(function (item) {
return {
label: item.label,
kind: monaco.languages.CompletionItemKind.Text,
documentation: item.documentation || "",
insertText: '"' + item.insertText + '"',
range: range
};
});
return {
suggestions: suggestions
};
})
.catch(function () {
return {
suggestions: []
};
}
const items = Array.isArray(result.suggestions.item) ? result.suggestions.item : [result.suggestions.item];
const suggestions = items.map(function (item) {
return {
label: item.label,
kind: monaco.languages.CompletionItemKind.Text,
documentation: item.documentation || "",
insertText: '"' + item.insertText + '"',
range: range
};
});
return {
suggestions: suggestions
};
})
.catch(function () {
return {
suggestions: []
};
});
}
function pushPromptMessage(role, content) {
promptMessages.push({
role: role,
content: content
});
}
require(["vs/editor/editor.main"], function () {
editor = monaco.editor.create(document.getElementById('container'), {
value: ['// lib/sayhello.js', 'function say() {', ' console.log("hello");', '}', '', 'exports.say = say;', '', 'exports.VERSIONINFO = "SayHello (sayhello.js) version 0.1";', 'exports.AUTHOR = "abuse@catswords.net";', 'exports.global = global;', 'exports.require = global.require;'].join('\n'),
language: 'javascript'
});
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: function (model, position) {
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
return getSuggestions(word.word, range);
}
});
});
window.addEventListener('resize', resizeEditor);
function getFileLanguage(fileName) {
const extension = fileName.split('.').pop().toLowerCase();
const languageMap = {
'js': 'javascript',
'ts': 'typescript',
'html': 'html',
'css': 'css',
'json': 'json',
'py': 'python',
'java': 'java',
'c': 'c',
'cpp': 'cpp',
'cs': 'csharp',
'php': 'php',
'rb': 'ruby',
'go': 'go',
'rs': 'rust'
};
return languageMap[extension] || 'plaintext';
}
function navigate(href) {
const a = document.createElement("a");
a.href = href;
a.target = "_blank";
document.body.appendChild(a);
a.click();
}
function appendTextToEditor(text) {
const position = editor.getPosition();
const range = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column);
editor.executeEdits("my-source", [{
range: range,
text: "\n" + text,
forceMoveMarkers: true
}]);
resizeEditor();
}
document.getElementById("fileInput").onchange = function (event) {
const file = event.target.files[0];
if (!file) return;
currentFileName = file.name;
const reader = new FileReader();
reader.onload = function (e) {
let language = getFileLanguage(file.name);
monaco.editor.setModelLanguage(editor.getModel(), language);
editor.setValue(e.target.result);
};
reader.readAsText(file);
};
document.getElementById("btnOpenFile").onclick = function () {
document.getElementById('fileInput').click();
};
document.getElementById("btnSaveFile").onclick = function () {
const text = editor.getValue();
const fileName = prompt("Enter file name:", currentFileName);
if (!fileName) return;
currentFileName = fileName;
const blob = new Blob([text], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
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}... Generating text with Copilot...`);
pushPromptMessage("user", promptMessage);
(async function () {
const targetWsUrl = await getTargetByUrl('copilot.microsoft.com');
if (targetWsUrl) {
await sendPromptMessage(targetWsUrl, promptMessage);
} else {
alert("Microsoft Copilot not running. Please visit copilot.microsoft.com first.");
}
})();
};
document.getElementById("btnAzureAi").onclick = function () {
const promptMessage = prompt("Enter a prompt message:", '');
if (!promptMessage || promptMessage.trim() == '') {
alert("A prompt message is required.");
return;
}
appendTextToEditor(`\n// ${promptMessage}... Generating text with Azure AI...`);
pushPromptMessage("user", promptMessage);
const apiKey = settings.AzureAiServiceApiKey;
const url = `${settings.AzureAiServicePrefix}models/chat/completions?api-version=${settings.AzureAiServiceApiVersion}`;
const data = {
messages: promptMessages,
max_tokens: 2048,
temperature: 0.8,
top_p: 0.1,
presence_penalty: 0,
frequency_penalty: 0,
model: 'Phi-4'
};
axios.post(url, data, {
headers: {
'Content-Type': 'application/json',
'api-key': apiKey
}
})
.then(response => {
response.data.choices.forEach(x => {
const responseContent = x.message.content;
pushPromptMessage("assistant", responseContent);
const responseText = DOMPurify.sanitize(responseContent, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
appendTextToEditor(`/*\n${responseText}\n*/`);
const pushPromptMessage = (role, content) => {
promptMessagesRef.current.push({
role: role,
content: content
});
})
.catch(error => {
console.error('Error:', error.response?.data || error.message);
});
};
document.getElementById("btnSavePrompt").onclick = function () {
const text = JSON.stringify(promptMessages, null, 4);
const blob = new Blob([text], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "prompt.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
document.getElementById("btnLoadPrompt").onclick = function () {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
fileInput.onchange = function (event) {
const file = event.target.files[0];
if (!file) return;
let reader = new FileReader();
reader.onload = function (e) {
promptMessages = JSON.parse(e.target.result);
appendTextToEditor("\n//Prompt loaded successfully.");
promptEditorRef.current.set(promptMessagesRef.current);
};
reader.readAsText(file);
};
fileInput.click();
};
document.getElementById("btnWhois").onclick = function () {
const hostname = prompt("Enter a hostname or IP address:", '');
if (!hostname || hostname.trim() == '') {
appendTextToEditor("\n// A hostname or IP address is required.");
return;
}
const navigate = (href) => {
const a = document.createElement("a");
a.href = href;
a.target = "_blank";
document.body.appendChild(a);
a.click();
};
axios.get(`${serverPrefix}whois/${hostname}`).then(response => {
const responseText = DOMPurify.sanitize(response.data, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
appendTextToEditor(`/*\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
});
};
const appendTextToEditor = (text) => {
const editor = editorRef.current;
const position = editor.getPosition();
const range = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column);
editor.executeEdits('', [{
range: range,
text: "\n" + text,
forceMoveMarkers: true
}]);
resizeEditor();
};
document.getElementById("btnDnsQuery").onclick = function () {
const hostname = prompt("Enter a hostname or IP address:", '');
if (!hostname || hostname.trim() == '') {
appendTextToEditor("\n// A hostname or IP address is required.");
return;
}
const openFile = () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.onchange = () => {
const file = fileInput.files[0];
axios.get(`${serverPrefix}dns-query/${hostname}`).then(response => {
const responseText = response.data;
appendTextToEditor(`/*\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
});
};
if (!file)
return;
async function getTargetByUrl(urlPart) {
const response = await fetch(`${serverPrefix}devtools/json`);
const targets = await response.json();
const reader = new FileReader();
reader.onload = (e) => {
const fileName = file.name;
const ext = fileName.split('.').pop().toLowerCase();
const langMap = {
js: 'javascript', ts: 'typescript', html: 'html',
css: 'css', json: 'json', py: 'python', java: 'java',
c: 'c', cpp: 'cpp', cs: 'csharp', php: 'php',
rb: 'ruby', go: 'go', rs: 'rust'
};
const lang = langMap[ext] || 'plaintext';
const target = targets.find(target => target.url.includes(urlPart));
monaco.editor.setModelLanguage(editorRef.current.getModel(), lang);
editorRef.current.setValue(e.target.result);
if (target) {
console.log(`Found target: ${target.title} (${target.id})`);
return target.webSocketDebuggerUrl;
} else {
console.log('Target not found');
return null;
}
}
fileNameRef.current = fileName;
};
reader.readAsText(file);
};
fileInput.click();
};
async function sendPromptMessage(wsUrl, promptMessage) {
const socket = new WebSocket(wsUrl);
const steps = [
{
id: 1,
method: 'Input.insertText',
params: {
text: promptMessage
const saveFile = () => {
const text = editorRef.current.getValue();
const fileName = prompt("Enter file name:", fileNameRef.current);
if (!fileName)
return;
const blob = new Blob([text], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileNameRef.current;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
const sendMessageToCopilot = () => {
const promptMessage = prompt("Enter a prompt message:", '');
if (!promptMessage || promptMessage.trim() == '') {
alert("A prompt message is required.");
return;
}
},
{
id: 2,
method: 'Input.dispatchKeyEvent',
params: {
type: 'keyDown',
key: 'Enter',
code: 'Enter'
}
},
{
id: 3,
method: 'Runtime.evaluate',
params: {
expression: '((e)=>e[e.length-1].querySelector("code")?.innerText||e[e.length-1].innerText)(document.querySelectorAll("[data-content=ai-message]"))'
}
}
];
socket.onopen = () => {
steps.forEach((step) => {
if (step.id == 3) {
setTimeout(() => {
socket.send(JSON.stringify(step));
}, 9000);
appendTextToEditor(`\n// ${promptMessage}... Generating text with Copilot...`);
pushPromptMessage("user", promptMessage);
(async () => {
const targetWsUrl = await getTargetByUrl('copilot.microsoft.com');
if (targetWsUrl) {
await _sendMessageToCopilot(targetWsUrl, promptMessage);
} else {
alert("Microsoft Copilot not running. Please visit copilot.microsoft.com first.");
}
})();
};
const _sendMessageToCopilot = async (wsUrl, promptMessage) => {
const socket = new WebSocket(wsUrl);
const steps = [
{
id: 1,
method: 'Input.insertText',
params: {
text: promptMessage
}
},
{
id: 2,
method: 'Input.dispatchKeyEvent',
params: {
type: 'keyDown',
key: 'Enter',
code: 'Enter'
}
},
{
id: 3,
method: 'Runtime.evaluate',
params: {
expression: '((e)=>e[e.length-1].querySelector("code")?.innerText||e[e.length-1].innerText)(document.querySelectorAll("[data-content=ai-message]"))'
}
}
];
socket.onopen = () => {
steps.forEach((step) => {
if (step.id == 3) {
setTimeout(() => {
socket.send(JSON.stringify(step));
}, 9000);
} else {
socket.send(JSON.stringify(step));
}
});
};
socket.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log("Sent successfully:", response.result);
if (response.id == 3) {
const responseContent = response.result.result.value;
appendTextToEditor(responseContent);
pushPromptMessage("assistant", responseContent);
socket.close();
}
};
};
const getTargetByUrl = async (urlPart) => {
const response = await fetch(`${serverPrefix}devtools/json`);
const targets = await response.json();
const target = targets.find(target => target.url.includes(urlPart));
if (target) {
console.log(`Found target: ${target.title} (${target.id})`);
return target.webSocketDebuggerUrl;
} else {
socket.send(JSON.stringify(step));
console.log('Target not found');
return null;
}
});
};
};
socket.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log("Sent successfully:", response.result);
const sendMessageToAzureAi = () => {
const promptMessage = prompt("Enter a prompt message:", '');
if (!promptMessage || promptMessage.trim() == '') {
alert("A prompt message is required.");
return;
}
if (response.id == 3) {
const responseContent = response.result.result.value;
appendTextToEditor(`\n// ${promptMessage}... Generating text with Azure AI...`);
pushPromptMessage("user", promptMessage);
appendTextToEditor(responseContent);
pushPromptMessage("assistant", responseContent);
const apiKey = settingsRef.current.AzureAiServiceApiKey;
const url = `${settingsRef.current.AzureAiServicePrefix}models/chat/completions?api-version=${settingsRef.current.AzureAiServiceApiVersion}`;
socket.close();
}
};
}
const data = {
messages: promptMessagesRef.current,
max_tokens: 2048,
temperature: 0.8,
top_p: 0.1,
presence_penalty: 0,
frequency_penalty: 0,
model: 'Phi-4'
};
fetchSettings();
axios.post(url, data, {
headers: {
'Content-Type': 'application/json',
'api-key': apiKey
}
}).then(response => {
response.data.choices.forEach(x => {
const responseContent = x.message.content;
pushPromptMessage("assistant", responseContent);
const responseText = DOMPurify.sanitize(responseContent, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
appendTextToEditor(`/*\n${responseText}\n*/`);
});
})
.catch(error => {
console.error('Error:', error.response?.data || error.message);
});
};
const savePromptMessages = () => {
const text = JSON.stringify(promptMessagesRef.current, null, 4);
const blob = new Blob([text], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "prompt.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
const loadPromptMessages = () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
fileInput.onchange = function (event) {
const file = event.target.files[0];
if (!file)
return;
let reader = new FileReader();
reader.onload = function (e) {
promptMessagesRef.current = JSON.parse(e.target.result);
promptEditorRef.current.set(promptMessagesRef.current);
appendTextToEditor("\n//Prompt loaded successfully.");
};
reader.readAsText(file);
};
fileInput.click();
};
const queryWhois = () => {
const hostname = prompt("Enter a hostname or IP address:", '');
if (!hostname || hostname.trim() == '') {
appendTextToEditor("\n// A hostname or IP address is required.");
return;
}
axios.get(`${serverPrefix}whois/${hostname}`).then(response => {
const responseText = DOMPurify.sanitize(response.data, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
appendTextToEditor(`/*\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
});
};
const queryDns = () => {
const hostname = prompt("Enter a hostname or IP address:", '');
if (!hostname || hostname.trim() == '') {
appendTextToEditor("\n// A hostname or IP address is required.");
return;
}
axios.get(`${serverPrefix}dns-query/${hostname}`).then(response => {
const responseText = response.data;
appendTextToEditor(`/*\n${responseText}\n*/`);
pushPromptMessage("system", responseText);
}).catch(error => {
console.error(error);
});
};
React.useEffect(() => {
window.addEventListener('resize', () => {
resizeEditor();
});
window.dispatchEvent(new Event('resize'));
}, []);
fetchSettings();
return _e('div', { className: 'app' },
_e(RibbonMenu, {
onOpenFileClick: openFile,
onSaveFileClick: saveFile,
onCopliotClick: sendMessageToCopilot,
onAzureAiClick: sendMessageToAzureAi,
onSavePromptClick: savePromptMessages,
onLoadPromptClick: loadPromptMessages,
onQueryWhoisClick: queryWhois,
onQueryDnsClick: queryDns
}),
_e('div', { id: 'container' },
_e(Editor, { editorRef }),
_e(PromptEditor, { promptEditorRef, promptMessagesRef })
),
_e('div', { className: 'banner' }, _e('a', { href: 'https://github.com/gnh1201/welsonjs' }, 'WelsonJS'), ' Editor powered by Metro UI, Monaco Editor, and JSONEditor.'),
);
}
const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(_e(App));
});
</script>
</body>
</html>