From 075dc44263f30ece8db3b978e5f63282fad6d657 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 19 Feb 2026 20:22:38 +0700 Subject: [PATCH 1/9] Add Minh Duc Cloud Elite extension --- extensions/minhduc/cloud-elite.js | 160 ++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 extensions/minhduc/cloud-elite.js diff --git a/extensions/minhduc/cloud-elite.js b/extensions/minhduc/cloud-elite.js new file mode 100644 index 0000000000..86a73dbb42 --- /dev/null +++ b/extensions/minhduc/cloud-elite.js @@ -0,0 +1,160 @@ +(function (Scratch) { + 'use strict'; + + class MinhDucCloudElite { + constructor() { + this.ws = null; + this.status = 'Disconnected'; + this.projectName = 'None'; + this.resolveMap = new Map(); + } + + getInfo() { + return { + id: 'minhduccloudelite', + name: 'Minh Duc Cloud Elite', + color1: '#0052ff', + color2: '#003eb3', + blocks: [ + // CONNECTION CATEGORY + { + opcode: 'connect', + blockType: Scratch.BlockType.COMMAND, + text: 'Connect to Project [ID]', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'lobby' } } + }, + { + opcode: 'disconnect', + blockType: Scratch.BlockType.COMMAND, + text: 'Disconnect' + }, + + '---', + + // REPORTER ACTIONS (Wait for server response) + { + opcode: 'getVar', + blockType: Scratch.BlockType.REPORTER, + text: 'Get [KEY]', + arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'score' } } + }, + { + opcode: 'setVar', + blockType: Scratch.BlockType.REPORTER, + text: 'Set [KEY] to [VAL]', + arguments: { + KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'hp' }, + VAL: { type: Scratch.ArgumentType.STRING, defaultValue: '100' } + } + }, + { + opcode: 'listVars', + blockType: Scratch.BlockType.REPORTER, + text: 'List All Variables' + }, + { + opcode: 'clearProject', + blockType: Scratch.BlockType.REPORTER, + text: 'Clear All Data (Wipe)' + }, + + '---', + + // MONITORING + { + opcode: 'readStatus', + blockType: Scratch.BlockType.REPORTER, + text: 'Connection Status' + }, + { + opcode: 'readProject', + blockType: Scratch.BlockType.REPORTER, + text: 'Current Project' + }, + { + opcode: 'getGuide', + blockType: Scratch.BlockType.REPORTER, + text: 'How to use? (Tutorial)' + } + ] + }; + } + + // --- CORE LOGIC --- + + connect(args) { + this.disconnect(); + const id = args.ID; + this.ws = new WebSocket(`wss://tuhbooh-cloudvariable.hf.space/api/${id}`); + this.status = 'Connecting...'; + + this.ws.onopen = () => { + this.status = 'Connected'; + this.projectName = id; + }; + + this.ws.onmessage = (event) => { + if (this.resolveMap.size > 0) { + const firstResolve = this.resolveMap.keys().next().value; + const timeout = this.resolveMap.get(firstResolve); + clearTimeout(timeout); + firstResolve(event.data); + this.resolveMap.delete(firstResolve); + } + }; + + this.ws.onclose = () => { + this.status = 'Disconnected'; + this.projectName = 'None'; + this.ws = null; + }; + + this.ws.onerror = () => { this.status = 'Connection Error'; }; + } + + disconnect() { + if (this.ws) { + this.ws.onclose = () => {}; + this.ws.close(); + this.ws = null; + } + this.status = 'Disconnected'; + this.projectName = 'None'; + for (let [resolve, timeout] of this.resolveMap) { + clearTimeout(timeout); + resolve("DISCONNECTED"); + } + this.resolveMap.clear(); + } + + // --- ASYNC REQUEST HANDLER --- + + async _request(msg) { + if (!this.ws || this.ws.readyState !== 1) return "OFFLINE"; + + return new Promise((resolve) => { + const timeout = setTimeout(() => { + this.resolveMap.delete(resolve); + resolve("TIMEOUT"); + }, 3000); + + this.resolveMap.set(resolve, timeout); + this.ws.send(msg); + }); + } + + async getVar(args) { return await this._request(`GET||${args.KEY}`); } + async setVar(args) { return await this._request(`SET||${args.KEY}||${args.VAL}`); } + async listVars() { return await this._request(`LIST`); } + async clearProject() { return await this._request(`CLEAR`); } + + readStatus() { return this.status; } + readProject() { return this.projectName; } + + getGuide() { + return "Step 1: Use 'Connect' block with a Project ID. | Step 2: Check 'Connection Status', wait for 'Connected'. | Step 3: Use the ROUND blocks (Get/Set/List) inside a 'Say' or 'Set Variable' block to perform actions and get results instantly."; + } + } + + Scratch.extensions.register(new MinhDucCloudElite()); +})(Scratch); From e2149d7ccb7f47920061f30535cd0da11a704033 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 19 Feb 2026 20:24:34 +0700 Subject: [PATCH 2/9] Rename cloud-elite.js to FlashCloud.js --- extensions/minhduc/{cloud-elite.js => FlashCloud.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extensions/minhduc/{cloud-elite.js => FlashCloud.js} (100%) diff --git a/extensions/minhduc/cloud-elite.js b/extensions/minhduc/FlashCloud.js similarity index 100% rename from extensions/minhduc/cloud-elite.js rename to extensions/minhduc/FlashCloud.js From 7100d26b4058b71260d326c01e7c2cf75311deeb Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 19 Feb 2026 20:33:35 +0700 Subject: [PATCH 3/9] Add string translation helper for block texts --- extensions/minhduc/FlashCloud.js | 110 ++++++++++++++++++------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/extensions/minhduc/FlashCloud.js b/extensions/minhduc/FlashCloud.js index 86a73dbb42..1aa0e62b01 100644 --- a/extensions/minhduc/FlashCloud.js +++ b/extensions/minhduc/FlashCloud.js @@ -1,6 +1,14 @@ (function (Scratch) { 'use strict'; + // Helper to translate strings + const formatMessage = (id, defaultText) => { + return Scratch.translate({ + id: id, + default: defaultText + }); + }; + class MinhDucCloudElite { constructor() { this.ws = null; @@ -12,36 +20,32 @@ getInfo() { return { id: 'minhduccloudelite', - name: 'Minh Duc Cloud Elite', + name: formatMessage('name', 'Min Duc Cloud Elite'), color1: '#0052ff', color2: '#003eb3', blocks: [ - // CONNECTION CATEGORY { opcode: 'connect', blockType: Scratch.BlockType.COMMAND, - text: 'Connect to Project [ID]', + text: formatMessage('connect', 'Connect to Project [ID]'), arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'lobby' } } }, { opcode: 'disconnect', blockType: Scratch.BlockType.COMMAND, - text: 'Disconnect' + text: formatMessage('disconnect', 'Disconnect') }, - '---', - - // REPORTER ACTIONS (Wait for server response) { opcode: 'getVar', blockType: Scratch.BlockType.REPORTER, - text: 'Get [KEY]', + text: formatMessage('getVar', 'Get [KEY]'), arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'score' } } }, { opcode: 'setVar', blockType: Scratch.BlockType.REPORTER, - text: 'Set [KEY] to [VAL]', + text: formatMessage('setVar', 'Set [KEY] to [VAL]'), arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'hp' }, VAL: { type: Scratch.ArgumentType.STRING, defaultValue: '100' } @@ -50,49 +54,53 @@ { opcode: 'listVars', blockType: Scratch.BlockType.REPORTER, - text: 'List All Variables' + text: formatMessage('listVars', 'List All Variables') }, { opcode: 'clearProject', blockType: Scratch.BlockType.REPORTER, - text: 'Clear All Data (Wipe)' + text: formatMessage('clearProject', 'Clear All Data (Wipe)') }, - '---', - - // MONITORING { opcode: 'readStatus', blockType: Scratch.BlockType.REPORTER, - text: 'Connection Status' + text: formatMessage('status', 'Connection Status') }, { opcode: 'readProject', blockType: Scratch.BlockType.REPORTER, - text: 'Current Project' + text: formatMessage('project', 'Current Project') }, { opcode: 'getGuide', blockType: Scratch.BlockType.REPORTER, - text: 'How to use? (Tutorial)' + text: formatMessage('guide', 'How to use? (Tutorial)') } ] }; } - // --- CORE LOGIC --- - - connect(args) { + async connect(args) { this.disconnect(); const id = args.ID; - this.ws = new WebSocket(`wss://tuhbooh-cloudvariable.hf.space/api/${id}`); + const url = `wss://tuhbooh-cloudvariable.hf.space/api/${id}`; + + // SECURITY CHECK REQUIRED BY TURBOWARP + if (!(await Scratch.canFetch(url))) { + this.status = 'Permission Denied'; + return; + } + + // eslint-disable-next-line extension/check-can-fetch + this.ws = new WebSocket(url); this.status = 'Connecting...'; - this.ws.onopen = () => { - this.status = 'Connected'; + this.ws.onopen = () => { + this.status = 'Connected'; this.projectName = id; }; - + this.ws.onmessage = (event) => { if (this.resolveMap.size > 0) { const firstResolve = this.resolveMap.keys().next().value; @@ -103,13 +111,15 @@ } }; - this.ws.onclose = () => { - this.status = 'Disconnected'; + this.ws.onclose = () => { + this.status = 'Disconnected'; this.projectName = 'None'; this.ws = null; }; - this.ws.onerror = () => { this.status = 'Connection Error'; }; + this.ws.onerror = () => { + this.status = 'Connection Error'; + }; } disconnect() { @@ -120,41 +130,53 @@ } this.status = 'Disconnected'; this.projectName = 'None'; - for (let [resolve, timeout] of this.resolveMap) { + for (const [resolve, timeout] of this.resolveMap) { clearTimeout(timeout); - resolve("DISCONNECTED"); + resolve('DISCONNECTED'); } this.resolveMap.clear(); } - // --- ASYNC REQUEST HANDLER --- - async _request(msg) { - if (!this.ws || this.ws.readyState !== 1) return "OFFLINE"; - - return new Promise((resolve) => { + if (!this.ws || this.ws.readyState !== 1) return 'OFFLINE'; + + const response = await new Promise((resolve) => { const timeout = setTimeout(() => { this.resolveMap.delete(resolve); - resolve("TIMEOUT"); - }, 3000); + resolve('TIMEOUT'); + }, 3000); this.resolveMap.set(resolve, timeout); this.ws.send(msg); }); + return response; + } + + async getVar(args) { + return await this._request(`GET||${args.KEY}`); + } + async setVar(args) { + return await this._request(`SET||${args.KEY}||${args.VAL}`); + } + async listVars() { + return await this._request(`LIST`); + } + async clearProject() { + return await this._request(`CLEAR`); } - async getVar(args) { return await this._request(`GET||${args.KEY}`); } - async setVar(args) { return await this._request(`SET||${args.KEY}||${args.VAL}`); } - async listVars() { return await this._request(`LIST`); } - async clearProject() { return await this._request(`CLEAR`); } - - readStatus() { return this.status; } - readProject() { return this.projectName; } + readStatus() { + return this.status; + } + readProject() { + return this.projectName; + } getGuide() { - return "Step 1: Use 'Connect' block with a Project ID. | Step 2: Check 'Connection Status', wait for 'Connected'. | Step 3: Use the ROUND blocks (Get/Set/List) inside a 'Say' or 'Set Variable' block to perform actions and get results instantly."; + return '1. Connect with ID. 2. Wait for Connected status. 3. Use round blocks to get/set data.'; } } Scratch.extensions.register(new MinhDucCloudElite()); })(Scratch); + From ec68326263575324332bc2648d04e680b193a7f2 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Thu, 19 Feb 2026 13:42:24 +0000 Subject: [PATCH 4/9] [Automated] Format code --- extensions/minhduc/FlashCloud.js | 99 ++++++++++++++++---------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/extensions/minhduc/FlashCloud.js b/extensions/minhduc/FlashCloud.js index 1aa0e62b01..78285b45d0 100644 --- a/extensions/minhduc/FlashCloud.js +++ b/extensions/minhduc/FlashCloud.js @@ -1,83 +1,87 @@ (function (Scratch) { - 'use strict'; + "use strict"; // Helper to translate strings const formatMessage = (id, defaultText) => { return Scratch.translate({ id: id, - default: defaultText + default: defaultText, }); }; class MinhDucCloudElite { constructor() { this.ws = null; - this.status = 'Disconnected'; - this.projectName = 'None'; + this.status = "Disconnected"; + this.projectName = "None"; this.resolveMap = new Map(); } getInfo() { return { - id: 'minhduccloudelite', - name: formatMessage('name', 'Min Duc Cloud Elite'), - color1: '#0052ff', - color2: '#003eb3', + id: "minhduccloudelite", + name: formatMessage("name", "Min Duc Cloud Elite"), + color1: "#0052ff", + color2: "#003eb3", blocks: [ { - opcode: 'connect', + opcode: "connect", blockType: Scratch.BlockType.COMMAND, - text: formatMessage('connect', 'Connect to Project [ID]'), - arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'lobby' } } + text: formatMessage("connect", "Connect to Project [ID]"), + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "lobby" }, + }, }, { - opcode: 'disconnect', + opcode: "disconnect", blockType: Scratch.BlockType.COMMAND, - text: formatMessage('disconnect', 'Disconnect') + text: formatMessage("disconnect", "Disconnect"), }, - '---', + "---", { - opcode: 'getVar', + opcode: "getVar", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('getVar', 'Get [KEY]'), - arguments: { KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'score' } } + text: formatMessage("getVar", "Get [KEY]"), + arguments: { + KEY: { type: Scratch.ArgumentType.STRING, defaultValue: "score" }, + }, }, { - opcode: 'setVar', + opcode: "setVar", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('setVar', 'Set [KEY] to [VAL]'), + text: formatMessage("setVar", "Set [KEY] to [VAL]"), arguments: { - KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'hp' }, - VAL: { type: Scratch.ArgumentType.STRING, defaultValue: '100' } - } + KEY: { type: Scratch.ArgumentType.STRING, defaultValue: "hp" }, + VAL: { type: Scratch.ArgumentType.STRING, defaultValue: "100" }, + }, }, { - opcode: 'listVars', + opcode: "listVars", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('listVars', 'List All Variables') + text: formatMessage("listVars", "List All Variables"), }, { - opcode: 'clearProject', + opcode: "clearProject", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('clearProject', 'Clear All Data (Wipe)') + text: formatMessage("clearProject", "Clear All Data (Wipe)"), }, - '---', + "---", { - opcode: 'readStatus', + opcode: "readStatus", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('status', 'Connection Status') + text: formatMessage("status", "Connection Status"), }, { - opcode: 'readProject', + opcode: "readProject", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('project', 'Current Project') + text: formatMessage("project", "Current Project"), }, { - opcode: 'getGuide', + opcode: "getGuide", blockType: Scratch.BlockType.REPORTER, - text: formatMessage('guide', 'How to use? (Tutorial)') - } - ] + text: formatMessage("guide", "How to use? (Tutorial)"), + }, + ], }; } @@ -88,16 +92,16 @@ // SECURITY CHECK REQUIRED BY TURBOWARP if (!(await Scratch.canFetch(url))) { - this.status = 'Permission Denied'; + this.status = "Permission Denied"; return; } // eslint-disable-next-line extension/check-can-fetch this.ws = new WebSocket(url); - this.status = 'Connecting...'; + this.status = "Connecting..."; this.ws.onopen = () => { - this.status = 'Connected'; + this.status = "Connected"; this.projectName = id; }; @@ -112,13 +116,13 @@ }; this.ws.onclose = () => { - this.status = 'Disconnected'; - this.projectName = 'None'; + this.status = "Disconnected"; + this.projectName = "None"; this.ws = null; }; this.ws.onerror = () => { - this.status = 'Connection Error'; + this.status = "Connection Error"; }; } @@ -128,22 +132,22 @@ this.ws.close(); this.ws = null; } - this.status = 'Disconnected'; - this.projectName = 'None'; + this.status = "Disconnected"; + this.projectName = "None"; for (const [resolve, timeout] of this.resolveMap) { clearTimeout(timeout); - resolve('DISCONNECTED'); + resolve("DISCONNECTED"); } this.resolveMap.clear(); } async _request(msg) { - if (!this.ws || this.ws.readyState !== 1) return 'OFFLINE'; + if (!this.ws || this.ws.readyState !== 1) return "OFFLINE"; const response = await new Promise((resolve) => { const timeout = setTimeout(() => { this.resolveMap.delete(resolve); - resolve('TIMEOUT'); + resolve("TIMEOUT"); }, 3000); this.resolveMap.set(resolve, timeout); @@ -173,10 +177,9 @@ } getGuide() { - return '1. Connect with ID. 2. Wait for Connected status. 3. Use round blocks to get/set data.'; + return "1. Connect with ID. 2. Wait for Connected status. 3. Use round blocks to get/set data."; } } Scratch.extensions.register(new MinhDucCloudElite()); })(Scratch); - From 44e668bd49e28f147df13c3ed48e1bed6240326e Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 21 Feb 2026 10:42:56 +0700 Subject: [PATCH 5/9] Update and rename FlashCloud.js to Translate+.js --- extensions/minhduc/FlashCloud.js | 185 ------------------------------- extensions/minhduc/Translate+.js | 134 ++++++++++++++++++++++ 2 files changed, 134 insertions(+), 185 deletions(-) delete mode 100644 extensions/minhduc/FlashCloud.js create mode 100644 extensions/minhduc/Translate+.js diff --git a/extensions/minhduc/FlashCloud.js b/extensions/minhduc/FlashCloud.js deleted file mode 100644 index 78285b45d0..0000000000 --- a/extensions/minhduc/FlashCloud.js +++ /dev/null @@ -1,185 +0,0 @@ -(function (Scratch) { - "use strict"; - - // Helper to translate strings - const formatMessage = (id, defaultText) => { - return Scratch.translate({ - id: id, - default: defaultText, - }); - }; - - class MinhDucCloudElite { - constructor() { - this.ws = null; - this.status = "Disconnected"; - this.projectName = "None"; - this.resolveMap = new Map(); - } - - getInfo() { - return { - id: "minhduccloudelite", - name: formatMessage("name", "Min Duc Cloud Elite"), - color1: "#0052ff", - color2: "#003eb3", - blocks: [ - { - opcode: "connect", - blockType: Scratch.BlockType.COMMAND, - text: formatMessage("connect", "Connect to Project [ID]"), - arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: "lobby" }, - }, - }, - { - opcode: "disconnect", - blockType: Scratch.BlockType.COMMAND, - text: formatMessage("disconnect", "Disconnect"), - }, - "---", - { - opcode: "getVar", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("getVar", "Get [KEY]"), - arguments: { - KEY: { type: Scratch.ArgumentType.STRING, defaultValue: "score" }, - }, - }, - { - opcode: "setVar", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("setVar", "Set [KEY] to [VAL]"), - arguments: { - KEY: { type: Scratch.ArgumentType.STRING, defaultValue: "hp" }, - VAL: { type: Scratch.ArgumentType.STRING, defaultValue: "100" }, - }, - }, - { - opcode: "listVars", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("listVars", "List All Variables"), - }, - { - opcode: "clearProject", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("clearProject", "Clear All Data (Wipe)"), - }, - "---", - { - opcode: "readStatus", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("status", "Connection Status"), - }, - { - opcode: "readProject", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("project", "Current Project"), - }, - { - opcode: "getGuide", - blockType: Scratch.BlockType.REPORTER, - text: formatMessage("guide", "How to use? (Tutorial)"), - }, - ], - }; - } - - async connect(args) { - this.disconnect(); - const id = args.ID; - const url = `wss://tuhbooh-cloudvariable.hf.space/api/${id}`; - - // SECURITY CHECK REQUIRED BY TURBOWARP - if (!(await Scratch.canFetch(url))) { - this.status = "Permission Denied"; - return; - } - - // eslint-disable-next-line extension/check-can-fetch - this.ws = new WebSocket(url); - this.status = "Connecting..."; - - this.ws.onopen = () => { - this.status = "Connected"; - this.projectName = id; - }; - - this.ws.onmessage = (event) => { - if (this.resolveMap.size > 0) { - const firstResolve = this.resolveMap.keys().next().value; - const timeout = this.resolveMap.get(firstResolve); - clearTimeout(timeout); - firstResolve(event.data); - this.resolveMap.delete(firstResolve); - } - }; - - this.ws.onclose = () => { - this.status = "Disconnected"; - this.projectName = "None"; - this.ws = null; - }; - - this.ws.onerror = () => { - this.status = "Connection Error"; - }; - } - - disconnect() { - if (this.ws) { - this.ws.onclose = () => {}; - this.ws.close(); - this.ws = null; - } - this.status = "Disconnected"; - this.projectName = "None"; - for (const [resolve, timeout] of this.resolveMap) { - clearTimeout(timeout); - resolve("DISCONNECTED"); - } - this.resolveMap.clear(); - } - - async _request(msg) { - if (!this.ws || this.ws.readyState !== 1) return "OFFLINE"; - - const response = await new Promise((resolve) => { - const timeout = setTimeout(() => { - this.resolveMap.delete(resolve); - resolve("TIMEOUT"); - }, 3000); - - this.resolveMap.set(resolve, timeout); - this.ws.send(msg); - }); - return response; - } - - async getVar(args) { - return await this._request(`GET||${args.KEY}`); - } - async setVar(args) { - return await this._request(`SET||${args.KEY}||${args.VAL}`); - } - async listVars() { - return await this._request(`LIST`); - } - async clearProject() { - return await this._request(`CLEAR`); - } - - readStatus() { - return this.status; - } - readProject() { - return this.projectName; - } - - getGuide() { - return "1. Connect with ID. 2. Wait for Connected status. 3. Use round blocks to get/set data."; - } - } - - Scratch.extensions.register(new MinhDucCloudElite()); -})(Scratch); diff --git a/extensions/minhduc/Translate+.js b/extensions/minhduc/Translate+.js new file mode 100644 index 0000000000..fb10af998b --- /dev/null +++ b/extensions/minhduc/Translate+.js @@ -0,0 +1,134 @@ +(function(Scratch) { + 'use strict'; + + class TranslatePlus { + getInfo() { + return { + id: 'translatePlus', + name: 'Translate+', + color1: '#4C97FF', + color2: '#3373CC', + blocks: [ + { + opcode: 'getTranslation', + blockType: Scratch.BlockType.REPORTER, + text: 'translate [TEXT] using [SERVICE] to [LANG]', + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'Hello' + }, + SERVICE: { + type: Scratch.ArgumentType.STRING, + menu: 'services', + defaultValue: 'Google' + }, + LANG: { + type: Scratch.ArgumentType.STRING, + menu: 'languages', + defaultValue: 'Vietnamese' + } + } + } + ], + menus: { + services: { + acceptReporters: true, + items: ['Google', 'Bing', 'Reverso', 'Alibaba'] + }, + languages: { + acceptReporters: true, + items: [ + 'Vietnamese', 'English', 'Chinese (Simplified)', 'Chinese (Traditional)', + 'Japanese', 'Korean', 'French', 'German', 'Russian', + 'Spanish', 'Portuguese', 'Italian', 'Thai', 'Lao', + 'Khmer', 'Indonesian', 'Malay', 'Arabic', 'Hindi', + 'Turkish', 'Dutch', 'Polish', 'Swedish', 'Danish', + 'Finnish', 'Norwegian', 'Greek', 'Hebrew', 'Bengali', + 'Persian', 'Urdu', 'Filipino', 'Romanian', 'Hungarian', + 'Czech', 'Ukrainian', 'Burmese', 'Swahili', 'Amharic', + 'Telugu', 'Tamil', 'Marathi', 'Gujarati', 'Kannada', + 'Malayalam', 'Punjabi', 'Slovak', 'Bulgarian', 'Croatian', + 'Serbian', 'Lithuanian', 'Latvian', 'Estonian', 'Slovenian', 'Icelandic' + ] + } + } + }; + } + + async getTranslation(args) { + const textInput = args.TEXT; + const serviceInput = args.SERVICE; + const langInput = args.LANG; + + // Cập nhật endpoint chính xác theo tài liệu curl mới: có thêm /gradio_api + const baseUrl = 'https://tuhbooh-translator.hf.space/gradio_api/call/translate'; + + const langMap = { + 'Vietnamese': 'Tiếng Việt', 'English': 'Tiếng Anh', 'Chinese (Simplified)': 'Tiếng Trung (Giản)', + 'Chinese (Traditional)': 'Tiếng Trung (Phồn)', 'Japanese': 'Tiếng Nhật', 'Korean': 'Tiếng Hàn', + 'French': 'Tiếng Pháp', 'German': 'Tiếng Đức', 'Russian': 'Tiếng Nga', 'Spanish': 'Tiếng Tây Ban Nha', + 'Portuguese': 'Tiếng Bồ Đào Nha', 'Italian': 'Tiếng Ý', 'Thai': 'Tiếng Thái', 'Lao': 'Tiếng Lào', + 'Khmer': 'Tiếng Khơ-me', 'Indonesian': 'Tiếng Indonesia', 'Malay': 'Tiếng Mã Lai', 'Arabic': 'Tiếng Ả Rập', + 'Hindi': 'Tiếng Ấn Độ (Hindi)', 'Turkish': 'Tiếng Thổ Nhĩ Kỳ', 'Dutch': 'Tiếng Hà Lan', 'Polish': 'Tiếng Ba Lan', + 'Swedish': 'Tiếng Thụy Điển', 'Danish': 'Tiếng Đan Mạch', 'Finnish': 'Tiếng Phần Lan', 'Norwegian': 'Tiếng Na Uy', + 'Greek': 'Tiếng Hy Lạp', 'Hebrew': 'Tiếng Do Thái', 'Bengali': 'Tiếng Bengali', 'Persian': 'Tiếng Ba Tư', + 'Urdu': 'Tiếng Urdu', 'Filipino': 'Tiếng Philippines', 'Romanian': 'Tiếng Rumani', 'Hungarian': 'Tiếng Hungari', + 'Czech': 'Tiếng Séc', 'Ukrainian': 'Tiếng Ukraina', 'Burmese': 'Tiếng Miến Điện', 'Swahili': 'Tiếng Swahili', + 'Amharic': 'Tiếng Amharic', 'Telugu': 'Tiếng Telugu', 'Tamil': 'Tiếng Tamil', 'Marathi': 'Tiếng Marathi', + 'Gujarati': 'Tiếng Gujarati', 'Kannada': 'Tiếng Kannada', 'Malayalam': 'Tiếng Malayalam', 'Punjabi': 'Tiếng Punjab', + 'Slovak': 'Tiếng Slovak', 'Bulgarian': 'Tiếng Bulgaria', 'Croatian': 'Tiếng Croatia', 'Serbian': 'Tiếng Serbia', + 'Lithuanian': 'Tiếng Litva', 'Latvian': 'Tiếng Latvia', 'Estonian': 'Tiếng Estonia', 'Slovenian': 'Tiếng Slovenia', + 'Icelandic': 'Tiếng Iceland' + }; + + const backendLangName = langMap[langInput] || langInput; + + try { + // BƯỚC 1: Gọi POST để lấy EVENT_ID (Khớp lệnh curl bước 1) + const callResponse = await fetch(baseUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + data: [textInput, serviceInput, backendLangName] + }) + }); + + if (!callResponse.ok) return "Error " + callResponse.status; + + const { event_id } = await callResponse.json(); + + // BƯỚC 2: Gọi GET để nhận kết quả (Khớp lệnh curl bước 2) + const resultResponse = await fetch(`${baseUrl}/${event_id}`); + + if (!resultResponse.ok) return "Result Error"; + + const rawResult = await resultResponse.text(); + + // Phân tích dữ liệu từ stream trả về của Gradio + // Nó sẽ trả về chuỗi có chứa "event: complete" và "data: [...]" + const dataLines = rawResult.split('\n'); + for (const line of dataLines) { + if (line.startsWith('data:')) { + const dataContent = line.replace('data:', '').trim(); + try { + const dataArray = JSON.parse(dataContent); + if (Array.isArray(dataArray) && dataArray.length > 0) { + return dataArray[0]; + } + } catch (e) { + continue; // Bỏ qua nếu dòng này không phải JSON hợp lệ + } + } + } + + return "Translation failed"; + } catch (e) { + console.error("Translate+ Error:", e); + return "Connect Failed"; + } + } + } + + Scratch.extensions.register(new TranslatePlus()); +})(Scratch); From 3a2b319070139c57a8d82183154adfbc04860dcb Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 21 Feb 2026 10:47:40 +0700 Subject: [PATCH 6/9] Implement translation system with formatMessage --- extensions/minhduc/Translate+.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/extensions/minhduc/Translate+.js b/extensions/minhduc/Translate+.js index fb10af998b..b1815f4f05 100644 --- a/extensions/minhduc/Translate+.js +++ b/extensions/minhduc/Translate+.js @@ -1,18 +1,27 @@ (function(Scratch) { 'use strict'; + // Thiết lập hệ thống dịch thuật cho extension (tránh lỗi extension/should-translate) + const formatMessage = (id, defaultMessage) => Scratch.translate({ + id, + default: defaultMessage, + description: `Translate+ extension: ${id}` + }); + class TranslatePlus { getInfo() { return { id: 'translatePlus', - name: 'Translate+', + // Bọc tên extension vào formatMessage + name: formatMessage('name', 'Translate+'), color1: '#4C97FF', color2: '#3373CC', blocks: [ { opcode: 'getTranslation', blockType: Scratch.BlockType.REPORTER, - text: 'translate [TEXT] using [SERVICE] to [LANG]', + // Bọc văn bản trên block vào formatMessage + text: formatMessage('blockText', 'translate [TEXT] using [SERVICE] to [LANG]'), arguments: { TEXT: { type: Scratch.ArgumentType.STRING, @@ -61,7 +70,6 @@ const serviceInput = args.SERVICE; const langInput = args.LANG; - // Cập nhật endpoint chính xác theo tài liệu curl mới: có thêm /gradio_api const baseUrl = 'https://tuhbooh-translator.hf.space/gradio_api/call/translate'; const langMap = { @@ -85,8 +93,8 @@ const backendLangName = langMap[langInput] || langInput; try { - // BƯỚC 1: Gọi POST để lấy EVENT_ID (Khớp lệnh curl bước 1) - const callResponse = await fetch(baseUrl, { + // Sử dụng Scratch.fetch() thay vì fetch() (tránh lỗi extension/use-scratch-fetch) + const callResponse = await Scratch.fetch(baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -98,15 +106,13 @@ const { event_id } = await callResponse.json(); - // BƯỚC 2: Gọi GET để nhận kết quả (Khớp lệnh curl bước 2) - const resultResponse = await fetch(`${baseUrl}/${event_id}`); + // Sử dụng Scratch.fetch() + const resultResponse = await Scratch.fetch(`${baseUrl}/${event_id}`); if (!resultResponse.ok) return "Result Error"; const rawResult = await resultResponse.text(); - // Phân tích dữ liệu từ stream trả về của Gradio - // Nó sẽ trả về chuỗi có chứa "event: complete" và "data: [...]" const dataLines = rawResult.split('\n'); for (const line of dataLines) { if (line.startsWith('data:')) { @@ -117,7 +123,7 @@ return dataArray[0]; } } catch (e) { - continue; // Bỏ qua nếu dòng này không phải JSON hợp lệ + continue; } } } From afdcfe7e69e418720aee43a8e4fff9c96d9240cc Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Sat, 21 Feb 2026 03:49:52 +0000 Subject: [PATCH 7/9] [Automated] Format code --- extensions/minhduc/Translate+.js | 217 ++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 64 deletions(-) diff --git a/extensions/minhduc/Translate+.js b/extensions/minhduc/Translate+.js index b1815f4f05..f02a5424a5 100644 --- a/extensions/minhduc/Translate+.js +++ b/extensions/minhduc/Translate+.js @@ -1,67 +1,115 @@ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; // Thiết lập hệ thống dịch thuật cho extension (tránh lỗi extension/should-translate) - const formatMessage = (id, defaultMessage) => Scratch.translate({ - id, - default: defaultMessage, - description: `Translate+ extension: ${id}` - }); + const formatMessage = (id, defaultMessage) => + Scratch.translate({ + id, + default: defaultMessage, + description: `Translate+ extension: ${id}`, + }); class TranslatePlus { getInfo() { return { - id: 'translatePlus', + id: "translatePlus", // Bọc tên extension vào formatMessage - name: formatMessage('name', 'Translate+'), - color1: '#4C97FF', - color2: '#3373CC', + name: formatMessage("name", "Translate+"), + color1: "#4C97FF", + color2: "#3373CC", blocks: [ { - opcode: 'getTranslation', + opcode: "getTranslation", blockType: Scratch.BlockType.REPORTER, // Bọc văn bản trên block vào formatMessage - text: formatMessage('blockText', 'translate [TEXT] using [SERVICE] to [LANG]'), + text: formatMessage( + "blockText", + "translate [TEXT] using [SERVICE] to [LANG]" + ), arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello' + defaultValue: "Hello", }, SERVICE: { type: Scratch.ArgumentType.STRING, - menu: 'services', - defaultValue: 'Google' + menu: "services", + defaultValue: "Google", }, LANG: { type: Scratch.ArgumentType.STRING, - menu: 'languages', - defaultValue: 'Vietnamese' - } - } - } + menu: "languages", + defaultValue: "Vietnamese", + }, + }, + }, ], menus: { services: { acceptReporters: true, - items: ['Google', 'Bing', 'Reverso', 'Alibaba'] + items: ["Google", "Bing", "Reverso", "Alibaba"], }, languages: { acceptReporters: true, items: [ - 'Vietnamese', 'English', 'Chinese (Simplified)', 'Chinese (Traditional)', - 'Japanese', 'Korean', 'French', 'German', 'Russian', - 'Spanish', 'Portuguese', 'Italian', 'Thai', 'Lao', - 'Khmer', 'Indonesian', 'Malay', 'Arabic', 'Hindi', - 'Turkish', 'Dutch', 'Polish', 'Swedish', 'Danish', - 'Finnish', 'Norwegian', 'Greek', 'Hebrew', 'Bengali', - 'Persian', 'Urdu', 'Filipino', 'Romanian', 'Hungarian', - 'Czech', 'Ukrainian', 'Burmese', 'Swahili', 'Amharic', - 'Telugu', 'Tamil', 'Marathi', 'Gujarati', 'Kannada', - 'Malayalam', 'Punjabi', 'Slovak', 'Bulgarian', 'Croatian', - 'Serbian', 'Lithuanian', 'Latvian', 'Estonian', 'Slovenian', 'Icelandic' - ] - } - } + "Vietnamese", + "English", + "Chinese (Simplified)", + "Chinese (Traditional)", + "Japanese", + "Korean", + "French", + "German", + "Russian", + "Spanish", + "Portuguese", + "Italian", + "Thai", + "Lao", + "Khmer", + "Indonesian", + "Malay", + "Arabic", + "Hindi", + "Turkish", + "Dutch", + "Polish", + "Swedish", + "Danish", + "Finnish", + "Norwegian", + "Greek", + "Hebrew", + "Bengali", + "Persian", + "Urdu", + "Filipino", + "Romanian", + "Hungarian", + "Czech", + "Ukrainian", + "Burmese", + "Swahili", + "Amharic", + "Telugu", + "Tamil", + "Marathi", + "Gujarati", + "Kannada", + "Malayalam", + "Punjabi", + "Slovak", + "Bulgarian", + "Croatian", + "Serbian", + "Lithuanian", + "Latvian", + "Estonian", + "Slovenian", + "Icelandic", + ], + }, + }, }; } @@ -69,25 +117,66 @@ const textInput = args.TEXT; const serviceInput = args.SERVICE; const langInput = args.LANG; - - const baseUrl = 'https://tuhbooh-translator.hf.space/gradio_api/call/translate'; - + + const baseUrl = + "https://tuhbooh-translator.hf.space/gradio_api/call/translate"; + const langMap = { - 'Vietnamese': 'Tiếng Việt', 'English': 'Tiếng Anh', 'Chinese (Simplified)': 'Tiếng Trung (Giản)', - 'Chinese (Traditional)': 'Tiếng Trung (Phồn)', 'Japanese': 'Tiếng Nhật', 'Korean': 'Tiếng Hàn', - 'French': 'Tiếng Pháp', 'German': 'Tiếng Đức', 'Russian': 'Tiếng Nga', 'Spanish': 'Tiếng Tây Ban Nha', - 'Portuguese': 'Tiếng Bồ Đào Nha', 'Italian': 'Tiếng Ý', 'Thai': 'Tiếng Thái', 'Lao': 'Tiếng Lào', - 'Khmer': 'Tiếng Khơ-me', 'Indonesian': 'Tiếng Indonesia', 'Malay': 'Tiếng Mã Lai', 'Arabic': 'Tiếng Ả Rập', - 'Hindi': 'Tiếng Ấn Độ (Hindi)', 'Turkish': 'Tiếng Thổ Nhĩ Kỳ', 'Dutch': 'Tiếng Hà Lan', 'Polish': 'Tiếng Ba Lan', - 'Swedish': 'Tiếng Thụy Điển', 'Danish': 'Tiếng Đan Mạch', 'Finnish': 'Tiếng Phần Lan', 'Norwegian': 'Tiếng Na Uy', - 'Greek': 'Tiếng Hy Lạp', 'Hebrew': 'Tiếng Do Thái', 'Bengali': 'Tiếng Bengali', 'Persian': 'Tiếng Ba Tư', - 'Urdu': 'Tiếng Urdu', 'Filipino': 'Tiếng Philippines', 'Romanian': 'Tiếng Rumani', 'Hungarian': 'Tiếng Hungari', - 'Czech': 'Tiếng Séc', 'Ukrainian': 'Tiếng Ukraina', 'Burmese': 'Tiếng Miến Điện', 'Swahili': 'Tiếng Swahili', - 'Amharic': 'Tiếng Amharic', 'Telugu': 'Tiếng Telugu', 'Tamil': 'Tiếng Tamil', 'Marathi': 'Tiếng Marathi', - 'Gujarati': 'Tiếng Gujarati', 'Kannada': 'Tiếng Kannada', 'Malayalam': 'Tiếng Malayalam', 'Punjabi': 'Tiếng Punjab', - 'Slovak': 'Tiếng Slovak', 'Bulgarian': 'Tiếng Bulgaria', 'Croatian': 'Tiếng Croatia', 'Serbian': 'Tiếng Serbia', - 'Lithuanian': 'Tiếng Litva', 'Latvian': 'Tiếng Latvia', 'Estonian': 'Tiếng Estonia', 'Slovenian': 'Tiếng Slovenia', - 'Icelandic': 'Tiếng Iceland' + Vietnamese: "Tiếng Việt", + English: "Tiếng Anh", + "Chinese (Simplified)": "Tiếng Trung (Giản)", + "Chinese (Traditional)": "Tiếng Trung (Phồn)", + Japanese: "Tiếng Nhật", + Korean: "Tiếng Hàn", + French: "Tiếng Pháp", + German: "Tiếng Đức", + Russian: "Tiếng Nga", + Spanish: "Tiếng Tây Ban Nha", + Portuguese: "Tiếng Bồ Đào Nha", + Italian: "Tiếng Ý", + Thai: "Tiếng Thái", + Lao: "Tiếng Lào", + Khmer: "Tiếng Khơ-me", + Indonesian: "Tiếng Indonesia", + Malay: "Tiếng Mã Lai", + Arabic: "Tiếng Ả Rập", + Hindi: "Tiếng Ấn Độ (Hindi)", + Turkish: "Tiếng Thổ Nhĩ Kỳ", + Dutch: "Tiếng Hà Lan", + Polish: "Tiếng Ba Lan", + Swedish: "Tiếng Thụy Điển", + Danish: "Tiếng Đan Mạch", + Finnish: "Tiếng Phần Lan", + Norwegian: "Tiếng Na Uy", + Greek: "Tiếng Hy Lạp", + Hebrew: "Tiếng Do Thái", + Bengali: "Tiếng Bengali", + Persian: "Tiếng Ba Tư", + Urdu: "Tiếng Urdu", + Filipino: "Tiếng Philippines", + Romanian: "Tiếng Rumani", + Hungarian: "Tiếng Hungari", + Czech: "Tiếng Séc", + Ukrainian: "Tiếng Ukraina", + Burmese: "Tiếng Miến Điện", + Swahili: "Tiếng Swahili", + Amharic: "Tiếng Amharic", + Telugu: "Tiếng Telugu", + Tamil: "Tiếng Tamil", + Marathi: "Tiếng Marathi", + Gujarati: "Tiếng Gujarati", + Kannada: "Tiếng Kannada", + Malayalam: "Tiếng Malayalam", + Punjabi: "Tiếng Punjab", + Slovak: "Tiếng Slovak", + Bulgarian: "Tiếng Bulgaria", + Croatian: "Tiếng Croatia", + Serbian: "Tiếng Serbia", + Lithuanian: "Tiếng Litva", + Latvian: "Tiếng Latvia", + Estonian: "Tiếng Estonia", + Slovenian: "Tiếng Slovenia", + Icelandic: "Tiếng Iceland", }; const backendLangName = langMap[langInput] || langInput; @@ -95,28 +184,28 @@ try { // Sử dụng Scratch.fetch() thay vì fetch() (tránh lỗi extension/use-scratch-fetch) const callResponse = await Scratch.fetch(baseUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - data: [textInput, serviceInput, backendLangName] - }) + data: [textInput, serviceInput, backendLangName], + }), }); if (!callResponse.ok) return "Error " + callResponse.status; - + const { event_id } = await callResponse.json(); // Sử dụng Scratch.fetch() const resultResponse = await Scratch.fetch(`${baseUrl}/${event_id}`); - + if (!resultResponse.ok) return "Result Error"; const rawResult = await resultResponse.text(); - - const dataLines = rawResult.split('\n'); + + const dataLines = rawResult.split("\n"); for (const line of dataLines) { - if (line.startsWith('data:')) { - const dataContent = line.replace('data:', '').trim(); + if (line.startsWith("data:")) { + const dataContent = line.replace("data:", "").trim(); try { const dataArray = JSON.parse(dataContent); if (Array.isArray(dataArray) && dataArray.length > 0) { From 13b1d691ed27ce51fb0a3970c46aa7838ef7ff90 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 22 Feb 2026 11:33:53 +0700 Subject: [PATCH 8/9] Add Cerebras AI extension for real-time chat --- extensions/minhduc/CerebrasAI.js | 146 +++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 extensions/minhduc/CerebrasAI.js diff --git a/extensions/minhduc/CerebrasAI.js b/extensions/minhduc/CerebrasAI.js new file mode 100644 index 0000000000..53845d4bc4 --- /dev/null +++ b/extensions/minhduc/CerebrasAI.js @@ -0,0 +1,146 @@ +(function(Scratch) { + 'use strict'; + + class CerebrasStreamPro { + constructor() { + this.chatHistory = []; + this.currentResponse = ""; + this.status = "Idle"; + this.systemPrompt = "You are a helpful assistant."; + } + + getInfo() { + return { + id: 'cerebrasStreamPro', + name: 'Cerebras AI (Real-time)', + color1: '#0ea5e9', // Professional Blue + color2: '#0284c7', + blocks: [ + { + opcode: 'setSystem', + blockType: Scratch.BlockType.COMMAND, + text: 'Setup System: [SYS]', + arguments: { + SYS: { type: Scratch.ArgumentType.STRING, defaultValue: 'You are a helpful assistant.' } + } + }, + { + opcode: 'streamChat', + blockType: Scratch.BlockType.COMMAND, + text: 'Ask Model [MODEL] Key [KEY]: [MSG]', + arguments: { + MODEL: { type: Scratch.ArgumentType.STRING, defaultValue: 'llama3.1-8b' }, + MSG: { type: Scratch.ArgumentType.STRING, defaultValue: 'Write a long story about a robot.' }, + KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'your-api-key' } + } + }, + { + opcode: 'getResponse', + blockType: Scratch.BlockType.REPORTER, + text: 'Live Response' + }, + { + opcode: 'isThinking', + blockType: Scratch.BlockType.BOOLEAN, + text: 'is AI thinking?' + }, + "--- History Management ---", + { + opcode: 'getHistoryJSON', + blockType: Scratch.BlockType.REPORTER, + text: 'Chat History (JSON)' + }, + { + opcode: 'importHistory', + blockType: Scratch.BlockType.COMMAND, + text: 'Import History [JSON]', + arguments: { + JSON: { type: Scratch.ArgumentType.STRING, defaultValue: '[]' } + } + }, + { + opcode: 'clearChat', + blockType: Scratch.BlockType.COMMAND, + text: 'Clear All Memory' + } + ] + }; + } + + setSystem(args) { + this.systemPrompt = args.SYS; + } + + async streamChat(args) { + const { MODEL, KEY, MSG } = args; + this.status = "Thinking"; + this.currentResponse = ""; // Reset for new stream + + this.chatHistory.push({ role: "user", content: MSG }); + + const messages = [ + { role: "system", content: this.systemPrompt }, + ...this.chatHistory + ]; + + try { + const response = await Scratch.fetch("https://api.cerebras.ai/v1/chat/completions", { + method: 'POST', + headers: { + 'Authorization': `Bearer ${KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: MODEL, + messages: messages, + stream: true // CRITICAL: Enable streaming + }) + }); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let fullAIResponse = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value); + const lines = chunk.split('\n').filter(line => line.trim() !== ''); + + for (const line of lines) { + const message = line.replace(/^data: /, ''); + if (message === '[DONE]') break; + + try { + const parsed = JSON.parse(message); + const delta = parsed.choices[0].delta.content; + if (delta) { + fullAIResponse += delta; + this.currentResponse = fullAIResponse; // Update the reporter in real-time + } + } catch (e) { + // Ignore partial JSON chunks + } + } + } + + this.chatHistory.push({ role: "assistant", content: fullAIResponse }); + this.status = "Idle"; + } catch (err) { + this.currentResponse = "Error: " + err.message; + this.status = "Idle"; + } + } + + getResponse() { return this.currentResponse; } + isThinking() { return this.status === "Thinking"; } + getHistoryJSON() { return JSON.stringify(this.chatHistory); } + clearChat() { this.chatHistory = []; this.currentResponse = ""; } + importHistory(args) { + try { this.chatHistory = JSON.parse(args.JSON); } catch(e) {} + } + } + + Scratch.extensions.register(new CerebrasStreamPro()); +})(Scratch); From f7f5668afe190335d28ea96ea07c8543bd770b30 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Sun, 22 Feb 2026 13:03:06 +0000 Subject: [PATCH 9/9] [Automated] Format code --- extensions/minhduc/CerebrasAI.js | 130 ++++++++++++++++++------------- 1 file changed, 78 insertions(+), 52 deletions(-) diff --git a/extensions/minhduc/CerebrasAI.js b/extensions/minhduc/CerebrasAI.js index 53845d4bc4..3236127c92 100644 --- a/extensions/minhduc/CerebrasAI.js +++ b/extensions/minhduc/CerebrasAI.js @@ -1,5 +1,5 @@ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; class CerebrasStreamPro { constructor() { @@ -11,59 +11,71 @@ getInfo() { return { - id: 'cerebrasStreamPro', - name: 'Cerebras AI (Real-time)', - color1: '#0ea5e9', // Professional Blue - color2: '#0284c7', + id: "cerebrasStreamPro", + name: "Cerebras AI (Real-time)", + color1: "#0ea5e9", // Professional Blue + color2: "#0284c7", blocks: [ { - opcode: 'setSystem', + opcode: "setSystem", blockType: Scratch.BlockType.COMMAND, - text: 'Setup System: [SYS]', + text: "Setup System: [SYS]", arguments: { - SYS: { type: Scratch.ArgumentType.STRING, defaultValue: 'You are a helpful assistant.' } - } + SYS: { + type: Scratch.ArgumentType.STRING, + defaultValue: "You are a helpful assistant.", + }, + }, }, { - opcode: 'streamChat', + opcode: "streamChat", blockType: Scratch.BlockType.COMMAND, - text: 'Ask Model [MODEL] Key [KEY]: [MSG]', + text: "Ask Model [MODEL] Key [KEY]: [MSG]", arguments: { - MODEL: { type: Scratch.ArgumentType.STRING, defaultValue: 'llama3.1-8b' }, - MSG: { type: Scratch.ArgumentType.STRING, defaultValue: 'Write a long story about a robot.' }, - KEY: { type: Scratch.ArgumentType.STRING, defaultValue: 'your-api-key' } - } + MODEL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "llama3.1-8b", + }, + MSG: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Write a long story about a robot.", + }, + KEY: { + type: Scratch.ArgumentType.STRING, + defaultValue: "your-api-key", + }, + }, }, { - opcode: 'getResponse', + opcode: "getResponse", blockType: Scratch.BlockType.REPORTER, - text: 'Live Response' + text: "Live Response", }, { - opcode: 'isThinking', + opcode: "isThinking", blockType: Scratch.BlockType.BOOLEAN, - text: 'is AI thinking?' + text: "is AI thinking?", }, "--- History Management ---", { - opcode: 'getHistoryJSON', + opcode: "getHistoryJSON", blockType: Scratch.BlockType.REPORTER, - text: 'Chat History (JSON)' + text: "Chat History (JSON)", }, { - opcode: 'importHistory', + opcode: "importHistory", blockType: Scratch.BlockType.COMMAND, - text: 'Import History [JSON]', + text: "Import History [JSON]", arguments: { - JSON: { type: Scratch.ArgumentType.STRING, defaultValue: '[]' } - } + JSON: { type: Scratch.ArgumentType.STRING, defaultValue: "[]" }, + }, }, { - opcode: 'clearChat', + opcode: "clearChat", blockType: Scratch.BlockType.COMMAND, - text: 'Clear All Memory' - } - ] + text: "Clear All Memory", + }, + ], }; } @@ -75,27 +87,30 @@ const { MODEL, KEY, MSG } = args; this.status = "Thinking"; this.currentResponse = ""; // Reset for new stream - + this.chatHistory.push({ role: "user", content: MSG }); const messages = [ { role: "system", content: this.systemPrompt }, - ...this.chatHistory + ...this.chatHistory, ]; try { - const response = await Scratch.fetch("https://api.cerebras.ai/v1/chat/completions", { - method: 'POST', - headers: { - 'Authorization': `Bearer ${KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: MODEL, - messages: messages, - stream: true // CRITICAL: Enable streaming - }) - }); + const response = await Scratch.fetch( + "https://api.cerebras.ai/v1/chat/completions", + { + method: "POST", + headers: { + Authorization: `Bearer ${KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: MODEL, + messages: messages, + stream: true, // CRITICAL: Enable streaming + }), + } + ); const reader = response.body.getReader(); const decoder = new TextDecoder(); @@ -106,11 +121,11 @@ if (done) break; const chunk = decoder.decode(value); - const lines = chunk.split('\n').filter(line => line.trim() !== ''); + const lines = chunk.split("\n").filter((line) => line.trim() !== ""); for (const line of lines) { - const message = line.replace(/^data: /, ''); - if (message === '[DONE]') break; + const message = line.replace(/^data: /, ""); + if (message === "[DONE]") break; try { const parsed = JSON.parse(message); @@ -133,12 +148,23 @@ } } - getResponse() { return this.currentResponse; } - isThinking() { return this.status === "Thinking"; } - getHistoryJSON() { return JSON.stringify(this.chatHistory); } - clearChat() { this.chatHistory = []; this.currentResponse = ""; } + getResponse() { + return this.currentResponse; + } + isThinking() { + return this.status === "Thinking"; + } + getHistoryJSON() { + return JSON.stringify(this.chatHistory); + } + clearChat() { + this.chatHistory = []; + this.currentResponse = ""; + } importHistory(args) { - try { this.chatHistory = JSON.parse(args.JSON); } catch(e) {} + try { + this.chatHistory = JSON.parse(args.JSON); + } catch (e) {} } }