From 2758971059146fa3a9362b647cb61de6e2a60037 Mon Sep 17 00:00:00 2001 From: km_awa <3213910856@qq.com> Date: Thu, 25 Jun 2026 19:32:25 +0800 Subject: [PATCH 1/2] Add AI extension --- extensions/KimosFrontender/ai.js | 1305 ++++++++++++++++++++++++++++++ extensions/extensions.json | 2 + images/KimosFrontender/ai.svg | 127 +++ samples/Ai.sb3 | Bin 0 -> 4365 bytes 4 files changed, 1434 insertions(+) create mode 100644 extensions/KimosFrontender/ai.js create mode 100644 images/KimosFrontender/ai.svg create mode 100644 samples/Ai.sb3 diff --git a/extensions/KimosFrontender/ai.js b/extensions/KimosFrontender/ai.js new file mode 100644 index 0000000000..1e54b6d07f --- /dev/null +++ b/extensions/KimosFrontender/ai.js @@ -0,0 +1,1305 @@ +// Name: AI +// ID: ai +// Description: Chat with AI in Scratch. Supports OpenAI and Anthropic APIs with streaming responses. +// By: KimosFrontender +// License: MIT + +// @ts-nocheck +(function (Scratch) { + // This extension is compatible down to ES6 at minimum. + "use strict" + var BLOCK_ICON_URI = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAADKUlEQVR4nO2b4XGcMBCFn9OAKeE68JVACddB6CBXgtPBdWCuA3cQ0gHu4NIBqeD5BzDmlF0hkNAyk9sZZjw6Cd5+aFcLyE8k8T/bN2sB1vYAYC3A2h4ArAVY2wOAtQBrswZwAXC0FGAJoAbwA0ADQwhWAM4Avg9/P6OHUFoIsQLg3vFnAL8AVLmFWAGoAFyF9jdkhmCZAyrsAMLWAAoAB8/vFYCfQvsbgNf0cgQjueVRk+xIHmf6VZSt3ljfpgDOE0d2C2GrE58ER3YJYYuTHgdnJesGJ33jS2V8Q7LYO4CC5E1xfmpzEDSIbWoIqQE0gmgNyC4gpHS+FsS+e37bBYRUzlcBItdCKIZzuRaSVLMAKBeIOwt9TSHEOn+gPEVLzxhptphBiHFeE3R2+kniYiA0CgQf9E0AvAtCasH5jnLSWgsBXJ9PkgG4CBdvnT4F78Oj5b+zwRzCGucl0Tfe32EtPC6B5yPJ1xwQljovrctSEgoJjxAIvjHj8aqMDQG4CIA7pUdzaWvhMVe4xEBolGsmBSBNaZdySHikhiDVFsGVYqjztXCRRuhX8n6WuOFxYB8ePnEShG4YG9o3uC6IAUDKyebIrweg06R9mhjn7tAUpOaQ9pywqChaEgIaBDcMRmc150drKd9VF6TkUGg+SgoA1Gv5embcQQBAritjlyyxyQGA67N1qlp+6RKbHIAPwlyC80E4ecaNx9olNjkAUH93Nyeo4LoKToJ+i3E+FgAY98ZmCYRS6LebFyI+CHMCNQjThKadPyRksgAA47J8pUCoqb9llmaJKQAwLstrEKQ7XyfUvMl3gbVZvlIcnlqTWO9mn8ZqxYFqZpwW7+QGH0W2BJAagvYwtGsAPghzZeuRX6GUZLmzAgDGl85zMybqeCKz/L9AhX7Xh2tXGGyMmlouAIAO4TeAE4Aul5Cp5QQA9NvjGvTb4qb2gX6fYHYIuXeJtegd/eu0v6AHU2TWY7JNboTwx2l/GX475BRjtU+wRR8OH057h8xhYLlRskM/E0YIJnnAerv8COEKoySYexXYnVnPAHN7ALAWYG0PANYCrO0BwFqAtX0CJHH8H1ebuYYAAAAASUVORK5CYII=' + var DATA_PROCESSING_URI = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAACTUlEQVR4nO2a4U3DMBBGH4gFwggwAoxQRggjlBHKCGWEdoQyQhmhjAAjNCMcP+IK54DWSXw5BH5SRVz5nM8fbmxffCYi/GfOvQV4UwzwFuBNMcBbgDfFAG8B3hQDvAV4M6UBN8AC2AMSrg/Mw3eH72dTibqY6D4r2k6msAx/n4F7GzmfTDECNqR3PqYOsbaIiOVnJl9ZHqm//Kb+zFKjtQEr1ZlFQsxCxWwsNZ6J7Xb4DbiKypdAkxC3B6pw3YQ4E6yfAXHnX0nrPMBLdF2pdrJiaYAWndr5vnVHURZC3gK8KQZ4C/CmGOAtwJtiwIT3GjO3V6erDOO3LoTeT7SVDUsDblRZd+oYuq5uKxtWm6GKdiMUD91r0k3Q8Q1w2yM+GYsRUANbup1f0098AzxF5Sq0WY9Wp8m8v36Tr2wz5hMk3CObZutZYA3cjYh/oDsSsmNtwBzYMXwa29LNHufHIM00E5GdGrZD0lo6P7gTkTq3XstZYEd3/r6lzQqlxu+jckM7i2RPlFj9BBra339Mn5cduu4aoyyR5TPgRZX7rOZ03dSR0xtLA/R/rM+DUBuQfQF0YMrN0JgNjVmStGyHvQV4UwzwFuBNMcBbgDfFAMO29eKlzzrALAmqsT4fMPQ9/585H6Df86ecFarpjgC9p8jKFBmhmBXHExyLUCfG1ACzszfRZyPfE58Xmv9Qx/R8kEyQE4T2rN/zgLg/c04Q2o7cAY+c3tk9hrrmnQf7WeDXUxZC3gK8KQZ4C/CmGOAtwJtigLcAb4oB3gK8+QAub0WlJ923VAAAAABJRU5ErkJggg==' + + class AI { + constructor() { + /** + * @type {Map} + */ + this.chats = new Map() + /** + * @type {Map} + */ + this.presets = new Map() + } + getInfo() { + return { + id: 'ai', + color1: '#3a3a3a', + color2: '#444444', + color3: '#777777', + name: Scratch.translate('AI'), + blocks: [ + { + opcode: 'isInternetConnected', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Internet connected?'), + blockType: Scratch.BlockType.BOOLEAN, + arguments: {}, + }, + { + opcode: 'createNewChat', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Create a new chat with id[ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'deleteChat', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Delete chat with id[ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'clearAllChats', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Clear all chats'), + blockType: Scratch.BlockType.COMMAND, + }, + { + opcode: 'createNewAIPreset', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Create a new AI preset with id [ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('preset1'), + }, + }, + }, + { + opcode: 'deletePreset', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Delete AI preset with id [ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('preset1'), + }, + }, + }, + { + opcode: 'clearAllPresets', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Clear all AI presets'), + blockType: Scratch.BlockType.COMMAND, + }, + { + opcode: 'setAiPresetProp', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Set the [PROP] of AI preset [ID] to [VALUE]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('preset1'), + }, + PROP: { + type: Scratch.ArgumentType.STRING, + menu: 'AI_PRESET_PROP', + defaultValue: 'apikey', + }, + VALUE: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'sk-xxxx', + }, + }, + }, + { + opcode: 'assignAiPresetToChat', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Use AI preset [PRESET_ID] for chat [CHAT_ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + PRESET_ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('preset1'), + }, + CHAT_ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'setRequestFormatOfPreset', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Set request format for preset with id[ID] to [FORMAT]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('preset1'), + }, + FORMAT: { + type: Scratch.ArgumentType.STRING, + menu: 'REQUEST_FORMATS', + defaultValue: 'openai', + }, + }, + }, + { + opcode: 'sendMessage', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Send message [MESSAGE] to chat with id[ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + MESSAGE: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('Hello'), + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'stopMessage', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Stop message from chat [ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'response', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Response from chat [ID]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'status', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Status of chat with id[ID]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'isIdle', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('Is chat with id[ID] idle?'), + blockType: Scratch.BlockType.BOOLEAN, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate('Common AI'), + }, + { + opcode: 'anthropicUrl', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('URL of [AI](Anthropic)'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + AI: { + type: Scratch.ArgumentType.STRING, + menu: 'COMMON_AI_ANTHROPIC_URLS', + defaultValue: 'https://api.deepseek.com/anthropic', + }, + }, + }, + { + opcode: 'openaiUrl', + blockIconURI: BLOCK_ICON_URI, + text: Scratch.translate('URL of [AI](OpenAI)'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + AI: { + type: Scratch.ArgumentType.STRING, + menu: 'COMMON_AI_OPENAI_URLS', + defaultValue: 'https://api.deepseek.com', + }, + }, + }, + + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate('Data processing(JSON)'), + }, + { + opcode: 'setDataOfMap', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Overwrite id[ID] in [MAP] with JSON [DATA]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'chat1', + }, + MAP: { + type: Scratch.ArgumentType.STRING, + menu: 'MAP', + defaultValue: 'chats', + }, + DATA: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{}', + }, + }, + }, + { + opcode: 'setDataOfChatHistory', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Overwrite chat history of id[ID] with JSON [DATA]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'chat1', + }, + DATA: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{}', + }, + }, + }, + { + opcode: 'clearChatHistory', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Clear chat history of id[ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'chat1', + }, + }, + }, + { + opcode: 'addMessageOfChatHistory', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate( + 'Add a message to chat [ID] with role[ROLE] and content[CONTENT]', + ), + blockType: Scratch.BlockType.COMMAND, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'chat1', + }, + ROLE: { + type: Scratch.ArgumentType.STRING, + menu: 'CHAT_ROLE', + defaultValue: 'user', + }, + CONTENT: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('How do you do?'), + }, + }, + }, + { + opcode: 'deleteItemOfChatHistory', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Delete message at index [INDEX] from chat with id[ID]'), + blockType: Scratch.BlockType.COMMAND, + arguments: { + INDEX: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'chat1', + }, + }, + }, + { + opcode: 'coverObject', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Cover [DIFF] to [BASE]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + DIFF: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{"key1": 1}', + }, + BASE: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{}', + }, + }, + }, + { + opcode: 'setObjectProp', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate("Set [OBJECT]'s [KEY] to [VALUE]"), + blockType: Scratch.BlockType.COMMAND, + arguments: { + OBJECT: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{}', + }, + KEY: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'key1', + }, + VALUE: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"value1"', + }, + }, + }, + { + opcode: 'allKeysOfMap', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('All keys of [MAP]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + MAP: { + type: Scratch.ArgumentType.STRING, + menu: 'MAP', + defaultValue: 'chats', + }, + }, + }, + { + opcode: 'dataOfMap', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Data of [ID] from [MAP]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + MAP: { + type: Scratch.ArgumentType.STRING, + menu: 'MAP', + defaultValue: 'chats', + }, + }, + }, + { + opcode: 'chatHistory', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Chat history from id[ID]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'propOfChatHistory', + blockIconURI: DATA_PROCESSING_URI, + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('[PROP] of item [INDEX] of chat history id[ID]'), + arguments: { + PROP: { + type: Scratch.ArgumentType.STRING, + menu: 'CHAT_HISTORY_PROP', + defaultValue: 'content', + }, + INDEX: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('chat1'), + }, + }, + }, + { + opcode: 'lengthOfArray', + blockIconURI: DATA_PROCESSING_URI, + text: Scratch.translate('Length of [ARRAY]'), + blockType: Scratch.BlockType.REPORTER, + arguments: { + ARRAY: { + type: Scratch.ArgumentType.STRING, + defaultValue: '[]', + }, + }, + }, + { + opcode: 'itemOfArray', + blockIconURI: DATA_PROCESSING_URI, + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('Item [INDEX] of array[ARRAY]'), + arguments: { + INDEX: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + ARRAY: { + type: Scratch.ArgumentType.STRING, + defaultValue: '[]', + }, + }, + }, + { + opcode: 'itemOfObject', + blockIconURI: DATA_PROCESSING_URI, + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('Item [KEY] of object[OBJECT]'), + arguments: { + KEY: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate('key1'), + }, + OBJECT: { + type: Scratch.ArgumentType.STRING, + defaultValue: '{}', + }, + }, + }, + ], + menus: { + MAP: { + acceptReporters: true, + items: [ + { + text: Scratch.translate('Chats'), + value: 'chats', + }, + { + text: Scratch.translate('AI presets'), + value: 'aipresets', + }, + ], + }, + AI_PRESET_PROP: { + acceptReporters: true, + items: [ + { + text: Scratch.translate('API Key'), + value: 'apikey', + }, + { + text: Scratch.translate('Request URL'), + value: 'requesturl', + }, + { + text: Scratch.translate('Model'), + value: 'model', + }, + ], + }, + REQUEST_FORMATS: { + acceptReporters: false, + items: [ + { + text: 'OpenAI', + value: 'openai', + }, + { + text: 'Anthropic', + value: 'anthropic', + }, + ], + }, + COMMON_AI_OPENAI_URLS: { + acceptReporters: false, + items: [ + { + text: 'OpenAI', + value: 'https://api.openai.com/v1', + }, + { + text: 'DeepSeek', + value: 'https://api.deepseek.com', + }, + { + text: 'Qwen', + value: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + }, + { + text: 'Kimi', + value: 'https://api.moonshot.cn/v1', + }, + { + text: 'GLM', + value: 'https://open.bigmodel.cn/api/paas/v4', + }, + { + text: 'Baichuan', + value: 'https://api.baichuan-ai.com/v1', + }, + { + text: 'Doubao', + value: 'https://ark.cn-beijing.volces.com/api/v3', + }, + { + text: 'MiniMax', + value: 'https://api.minimax.chat/v1', + }, + { + text: 'Stepfun', + value: 'https://api.stepfun.com/v1', + }, + { + text: 'Hunyuan', + value: 'https://api.hunyuan.cloud.tencent.com/v1', + }, + { + text: 'Spark', + value: 'https://spark-api-open.xf-yun.com/v1', + }, + { + text: 'Gemini', + value: 'https://generativelanguage.googleapis.com/v1beta/openai', + }, + { + text: 'Groq', + value: 'https://api.groq.com/openai/v1', + }, + { + text: 'Mistral', + value: 'https://api.mistral.ai/v1', + }, + { + text: 'Cohere', + value: 'https://api.cohere.ai/compatibility/v1', + }, + { + text: 'Grok', + value: 'https://api.x.ai/v1', + }, + { + text: 'OpenRouter', + value: 'https://openrouter.ai/api/v1', + }, + { + text: 'Together AI', + value: 'https://api.together.xyz/v1', + }, + { + text: 'Fireworks AI', + value: 'https://api.fireworks.ai/inference/v1', + }, + { + text: 'Perplexity', + value: 'https://api.perplexity.ai', + }, + { + text: 'Novita AI', + value: 'https://api.novita.ai/v3/openai', + }, + { + text: 'SiliconFlow', + value: 'https://api.siliconflow.cn/v1', + }, + { + text: 'Vercel AI Gateway', + value: 'https://ai-gateway.vercel.sh/v1', + }, + ], + }, + + COMMON_AI_ANTHROPIC_URLS: { + acceptReporters: false, + items: [ + { + text: 'DeepSeek', + value: 'https://api.deepseek.com/anthropic', + }, + { + text: 'Grok', + value: 'https://api.x.ai', + }, + { + text: 'GLM ', + value: 'https://open.bigmodel.cn/api/anthropic', + }, + { + text: 'Qwen', + value: 'https://dashscope.aliyuncs.com/apps/anthropic', + }, + { + text: 'Mimo', + value: 'https://token-plan-cn.xiaomimimo.com', + }, + { + text: 'Gemini', + value: 'https://generativelanguage.googleapis.com/v1beta/openai', + }, + { + text: 'MiniMax', + value: 'https://api.minimaxi.com/anthropic', + }, + { + text: 'Seed Code', + value: 'https://ark.cn-beijing.volces.com/api/compatible', + }, + { + text: 'Kimi', + value: 'https://api.moonshot.cn', + }, + { + text: 'Claude', + value: 'https://api.anthropic.com', + }, + { + text: 'OpenRouter', + value: 'https://openrouter.ai/api', + }, + { + text: 'OfoxAI', + value: 'https://api.ofox.io/anthropic', + }, + { + text: 'Vercel', + value: 'https://ai-gateway.vercel.sh', + }, + { + text: 'Cloudflare AI', + value: + 'https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY_ID/anthropic', + }, + ], + }, + + COMMON_MODELS: { + acceptReporters: true, + items: [], + }, + CHAT_HISTORY_PROP: { + acceptReporters: true, + items: [ + { + text: Scratch.translate('role'), + value: 'role', + }, + { + text: Scratch.translate('content'), + value: 'content', + }, + ], + }, + CHAT_ROLE: { + acceptReporters: false, + items: [ + { + text: Scratch.translate('user'), + value: 'user', + }, + { + text: Scratch.translate('assistant'), + value: 'assistant', + }, + ], + }, + }, + } + } + /** + * @param {string} url + * @param {"anthropic"|"openai"} format + * @returns {string} + */ + _padUrl(url, format = 'anthropic') { + if (typeof url !== 'string') return '' + var result = url + while (result.endsWith('/')) { + result = result.slice(0, -1) + } + switch (format) { + case 'anthropic': + if (result.endsWith('/v1')) { + result += '/messages' + } else if (!result.endsWith('/v1/messages')) { + result += '/v1/messages' + } + break + case 'openai': + // OpenAI-compatible APIs expect the path to end with /v1/chat/completions + if (!result.endsWith('/v1/chat/completions')) { + if (result.endsWith('/v1')) { + result += '/chat/completions' + } else { + result += '/v1/chat/completions' + } + } + break + default: + break + } + return result + } + _sendAnthropic(chat, requesturl, headers, body) { + function fetchHandle(response) { + chat.messages.push({ + role: 'assistant', + content: '', + }) + if (!response.ok) { + chat.messages[chat.messages.length - 1].content = 'Request failed:' + response.status + chat.status = 'idle' + return + } + var reader = response.body.getReader() + var decoder = new TextDecoder() + var buffer = '' + function readHandle(result) { + if (result.done) { + chat.messages[chat.messages.length - 1].content = chat.response + chat.status = 'idle' + chat.response = null + return + } + buffer += decoder.decode(result.value, { stream: true }) + var lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (var i = 0; i < lines.length; i++) { + var line = lines[i] + if (!line.startsWith('data: ')) continue + var jsonStr = line.slice(6) + + try { + var data = JSON.parse(jsonStr) + } catch (e) { + continue + } + + switch (data.type) { + case 'content_block_start': + break + case 'content_block_delta': + if (data.delta && data.delta.text) { + chat.response += data.delta.text + chat.messages[chat.messages.length - 1].content = chat.response + } + break + case 'content_block_stop': + break + case 'message_delta': + break + case 'message_stop': + break + case 'error': + chat.messages[chat.messages.length - 1].content += '[error]' + chat.status = 'idle' + chat.response = null + break + } + } + readStream() + } + function readError(error) { + if (error.name !== 'AbortError') { + chat.messages[chat.messages.length - 1].content += '[break]' + } else { + chat.messages[chat.messages.length - 1].content = chat.response + } + chat.status = 'idle' + chat.response = null + } + function readStream() { + reader.read().then(readHandle).catch(readError) + } + readStream() + } + function fetchError(error) { + if (error.name === 'AbortError') { + chat.messages.push({ role: 'assistant', content: chat.response }) + } else { + chat.messages.push({ + role: 'assistant', + content: 'Request failed: ' + error.message, + }) + } + chat.status = 'idle' + chat.response = null + } + + Scratch.fetch(requesturl, { + method: 'POST', + headers: headers, + body: JSON.stringify(body), + signal: chat.controller.signal, + }) + .then(fetchHandle) + .catch(fetchError) + } + // Send a streaming request using the OpenAI-compatible chat completions format. + // The structure mirrors _sendAnthropic above: fetch -> read SSE stream -> accumulate deltas. + _sendOpenai(chat, requesturl, headers, body) { + function fetchHandle(response) { + chat.messages.push({ + role: 'assistant', + content: '', + }) + if (!response.ok) { + chat.messages[chat.messages.length - 1].content = 'Request failed:' + response.status + chat.status = 'idle' + return + } + var reader = response.body.getReader() + var decoder = new TextDecoder() + var buffer = '' + function readHandle(result) { + if (result.done) { + chat.messages[chat.messages.length - 1].content = chat.response + chat.status = 'idle' + chat.response = null + return + } + buffer += decoder.decode(result.value, { stream: true }) + var lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (var i = 0; i < lines.length; i++) { + var line = lines[i] + // Each SSE event starts with "data: " followed by JSON + if (!line.startsWith('data: ')) continue + var jsonStr = line.slice(6) + + // "[DONE]" marker signals the end of the stream + if (jsonStr.trim() === '[DONE]') { + // Stream finished normally — final content is already in chat.response + continue + } + + try { + var data = JSON.parse(jsonStr) + } catch (e) { + continue + } + + // OpenAI-style SSE: data.choices[0].delta.content + if ( + data.choices && + data.choices[0] && + data.choices[0].delta && + typeof data.choices[0].delta.content === 'string' + ) { + chat.response += data.choices[0].delta.content + chat.messages[chat.messages.length - 1].content = chat.response + } + } + readStream() + } + function readError(error) { + if (error.name !== 'AbortError') { + chat.messages[chat.messages.length - 1].content += '[break]' + } else { + chat.messages[chat.messages.length - 1].content = chat.response + } + chat.status = 'idle' + chat.response = null + } + function readStream() { + reader.read().then(readHandle).catch(readError) + } + readStream() + } + function fetchError(error) { + if (error.name === 'AbortError') { + chat.messages.push({ role: 'assistant', content: chat.response }) + } else { + chat.messages.push({ + role: 'assistant', + content: 'Request failed: ' + error.message, + }) + } + chat.status = 'idle' + chat.response = null + } + + Scratch.fetch(requesturl, { + method: 'POST', + headers: headers, + body: JSON.stringify(body), + signal: chat.controller.signal, + }) + .then(fetchHandle) + .catch(fetchError) + } + isInternetConnected() { + return navigator.onLine || false + } + deletePreset(args) { + var ID = Scratch.Cast.toString(args.ID) + if (!this.presets.has(ID)) return + this.presets.delete(ID) + } + clearAllPresets() { + this.presets.clear() + } + clearAllChats() { + this.chats.clear() + } + deleteChat(args) { + var ID = Scratch.Cast.toString(args.ID) + if (!this.chats.has(ID)) return + this.chats.delete(ID) + } + setRequestFormatOfPreset(args) { + var ID = Scratch.Cast.toString(args.ID) + var FORMAT = Scratch.Cast.toString(args.FORMAT) + if (!this.presets.has(ID)) return + this.presets.get(ID).requestformat = FORMAT + } + createNewChat(args) { + var ID = Scratch.Cast.toString(args.ID) + if (this.chats.has(ID)) return + this.chats.set(ID, { + id: ID, + aiPresetId: '', + messages: [], + status: 'idle', + controller: null, + response: null, + }) + } + status(args) { + const ID = Scratch.Cast.toString(args.ID) + if (!this.chats.has(ID)) return '' + return this.chats.get(ID).status + } + isIdle(args) { + const ID = Scratch.Cast.toString(args.ID) + if (!this.chats.has(ID)) return false + return this.chats.get(ID).status === 'idle' + } + createNewAIPreset(args) { + var ID = Scratch.Cast.toString(args.ID) + if (this.presets.has(ID)) return + this.presets.set(ID, { + id: ID, + requestformat: 'openai', + requesturl: '', + model: '', + apikey: '', + }) + } + setAiPresetProp(args) { + var ID = Scratch.Cast.toString(args.ID) + var currentPreset = this.presets.get(ID) + if (!currentPreset) return + var PROP = Scratch.Cast.toString(args.PROP) + var VALUE = Scratch.Cast.toString(args.VALUE) + switch (PROP) { + case 'apikey': + currentPreset.apikey = VALUE + break + case 'model': + currentPreset.model = VALUE + break + case 'requesturl': + currentPreset.requesturl = VALUE + break + case 'requestformat': + currentPreset.requestformat = VALUE + break + default: + return + } + } + assignAiPresetToChat(args) { + var PRESET_ID = Scratch.Cast.toString(args.PRESET_ID) + var CHAT_ID = Scratch.Cast.toString(args.CHAT_ID) + var chat = this.chats.get(CHAT_ID) + if (!chat) return + chat.aiPresetId = PRESET_ID + } + + sendMessage(args) { + var MESSAGE = Scratch.Cast.toString(args.MESSAGE) + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return + if (chat.status !== 'idle') return + var preset = this.presets.get(chat.aiPresetId) + if (!preset) { + chat.messages.push({ + role: 'assistant', + content: 'Error: No AI preset assigned', + }) + chat.status = 'idle' + return + } + var format = preset.requestformat + chat.status = 'pending' + var message = { + role: 'user', + content: MESSAGE, + } + chat.controller = new AbortController() + chat.messages.push(message) + chat.response = '' + + var requesturl = this._padUrl(preset.requesturl, format) + var headers + var body + + if (format === 'anthropic') { + headers = { + 'Content-Type': 'application/json', + 'x-api-key': preset.apikey, + 'anthropic-version': '2023-06-01', + 'anthropic-dangerous-direct-browser-access': 'true', + } + body = { + model: preset.model, + messages: chat.messages.map(function (msg) { + return { role: msg.role, content: msg.content } + }), + stream: true, + max_tokens: 9070, + } + this._sendAnthropic(chat, requesturl, headers, body) + } else if (format === 'openai') { + // OpenAI-compatible chat completions format + headers = { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + preset.apikey, + } + body = { + model: preset.model, + messages: chat.messages.map(function (msg) { + return { role: msg.role, content: msg.content } + }), + stream: true, + } + this._sendOpenai(chat, requesturl, headers, body) + } + } + stopMessage(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return + if (chat.controller) chat.controller.abort() + } + + response(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return '' + return chat.response || '' + } + anthropicUrl(args) { + return Scratch.Cast.toString(args.AI) + } + openaiUrl(args) { + return Scratch.Cast.toString(args.AI) + } + addMessageOfChatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var ROLE = Scratch.Cast.toString(args.ROLE) + var CONTENT = Scratch.Cast.toString(args.CONTENT) + var chat = this.chats.get(ID) + if (!chat) return + chat.messages.push({ role: ROLE, content: CONTENT }) + } + deleteItemOfChatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) + if (!chat) return + // Convert 1-based index to 0-based + chat.messages.splice(INDEX - 1, 1) + } + clearChatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return + chat.messages = [] + } + setDataOfChatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return + var data + try { + data = JSON.parse(Scratch.Cast.toString(args.DATA)) + if (!Array.isArray(data)) return + } catch { + return + } + chat.messages = data + } + coverObject(args) { + var base + var diff + // In terms of performance, using a for loop directly is better than Object.assign. + try { + base = JSON.parse(Scratch.Cast.toString(args.BASE)) + diff = JSON.parse(Scratch.Cast.toString(args.DIFF)) + for (var i in diff) { + // In this situation, there's no need to worry about using Object.prototype.hasOwnProperty.call. + base[i] = diff[i] + } + } catch { + return '{}' + } + return JSON.stringify(base) + } + setObjectProp(args) { + var obj + var prop + var value + try { + obj = JSON.parse(Scratch.Cast.toString(args.OBJECT)) + prop = Scratch.Cast.toString(args.KEY) + value = JSON.parse(Scratch.Cast.toString(args.VALUE)) + obj[prop] = value + } catch { + return '{}' + } + return JSON.stringify(obj) + } + allKeysOfMap(args) { + var MAP = Scratch.Cast.toString(args.MAP) + switch (MAP) { + case 'chats': + return JSON.stringify(Array.from(this.chats.keys())) + case 'aipresets': + return JSON.stringify(Array.from(this.presets.keys())) + default: + return '[]' + } + } + dataOfMap(args) { + var ID = Scratch.Cast.toString(args.ID) + var MAP = Scratch.Cast.toString(args.MAP) + var currentMap + switch (MAP) { + case 'chats': + currentMap = this.chats.get(ID) + break + case 'aipresets': + currentMap = this.presets.get(ID) + break + default: + return '{}' + } + if (!currentMap) return '{}' + return JSON.stringify(currentMap) + } + setDataOfMap(args) { + var ID = Scratch.Cast.toString(args.ID) + var MAP = Scratch.Cast.toString(args.MAP) + var DATA = Scratch.Cast.toString(args.DATA) + var currentMap + var parsedData + switch (MAP) { + case 'chats': + currentMap = this.chats + break + case 'aipresets': + currentMap = this.presets + break + default: + return + } + try { + parsedData = JSON.parse(DATA) + } catch { + return + } + if (!(typeof parsedData === 'object')) return + if (!Array.isArray(parsedData)) return + currentMap.set(ID, parsedData) + } + chatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var chat = this.chats.get(ID) + if (!chat) return '[]' + return JSON.stringify(chat.messages) + } + propOfChatHistory(args) { + var ID = Scratch.Cast.toString(args.ID) + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) + var PROP = Scratch.Cast.toString(args.PROP) + var chat = this.chats.get(ID) + if (!chat) return '' + if (INDEX < 1 || INDEX > chat.messages.length) return '' + var msg = chat.messages[INDEX - 1] + return msg[PROP] || '' + } + lengthOfArray(args) { + var ARRAY = Scratch.Cast.toString(args.ARRAY) + var parsedArray + try { + parsedArray = JSON.parse(ARRAY) + } catch { + parsedArray = [] + } + return parsedArray.length + } + itemOfArray(args) { + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) + var ARRAY = Scratch.Cast.toString(args.ARRAY) + var parsedArray + try { + parsedArray = JSON.parse(ARRAY) + } catch { + parsedArray = [] + } + var item = parsedArray[INDEX - 1] + if (typeof item === 'undefined') return '' + return item + } + itemOfObject(args) { + var KEY = Scratch.Cast.toString(args.KEY) + var OBJECT = Scratch.Cast.toString(args.OBJECT) + var parsedObject + try { + parsedObject = JSON.parse(OBJECT) + } catch { + parsedObject = {} + } + var item = parsedObject[KEY] + if (typeof item === 'undefined') return '' + return item + } + } + Scratch.extensions.register(new AI()) +})(Scratch) diff --git a/extensions/extensions.json b/extensions/extensions.json index 5f766f06d1..1119e6f1a1 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -25,6 +25,7 @@ "clipboard", "obviousAlexC/penPlus", "penplus", + "Xeltalliv/simple3D", "Lily/Skins", "obviousAlexC/SensingPlus", @@ -48,6 +49,7 @@ "mdwalters/notifications", "XeroName/Deltatime", "ar", + "KimosFrontender/ai", "encoding", "SharkPool/Tune-Shark-V3", "Lily/SoundExpanded", diff --git a/images/KimosFrontender/ai.svg b/images/KimosFrontender/ai.svg new file mode 100644 index 0000000000..4bdbd1f0f0 --- /dev/null +++ b/images/KimosFrontender/ai.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Ai.sb3 b/samples/Ai.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..2c7f92b9368706423d88f02aaae7610c06f81859 GIT binary patch literal 4365 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 literal 0 HcmV?d00001 From 098bdfc6c3c2448e4a03264eb8f2e562be61c52b Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Thu, 25 Jun 2026 11:50:11 +0000 Subject: [PATCH 2/2] [Automated] Format code --- extensions/KimosFrontender/ai.js | 1117 +++++++++++++++--------------- 1 file changed, 569 insertions(+), 548 deletions(-) diff --git a/extensions/KimosFrontender/ai.js b/extensions/KimosFrontender/ai.js index 1e54b6d07f..c45d076b7a 100644 --- a/extensions/KimosFrontender/ai.js +++ b/extensions/KimosFrontender/ai.js @@ -7,11 +7,11 @@ // @ts-nocheck (function (Scratch) { // This extension is compatible down to ES6 at minimum. - "use strict" + "use strict"; var BLOCK_ICON_URI = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAADKUlEQVR4nO2b4XGcMBCFn9OAKeE68JVACddB6CBXgtPBdWCuA3cQ0gHu4NIBqeD5BzDmlF0hkNAyk9sZZjw6Cd5+aFcLyE8k8T/bN2sB1vYAYC3A2h4ArAVY2wOAtQBrswZwAXC0FGAJoAbwA0ADQwhWAM4Avg9/P6OHUFoIsQLg3vFnAL8AVLmFWAGoAFyF9jdkhmCZAyrsAMLWAAoAB8/vFYCfQvsbgNf0cgQjueVRk+xIHmf6VZSt3ljfpgDOE0d2C2GrE58ER3YJYYuTHgdnJesGJ33jS2V8Q7LYO4CC5E1xfmpzEDSIbWoIqQE0gmgNyC4gpHS+FsS+e37bBYRUzlcBItdCKIZzuRaSVLMAKBeIOwt9TSHEOn+gPEVLzxhptphBiHFeE3R2+kniYiA0CgQf9E0AvAtCasH5jnLSWgsBXJ9PkgG4CBdvnT4F78Oj5b+zwRzCGucl0Tfe32EtPC6B5yPJ1xwQljovrctSEgoJjxAIvjHj8aqMDQG4CIA7pUdzaWvhMVe4xEBolGsmBSBNaZdySHikhiDVFsGVYqjztXCRRuhX8n6WuOFxYB8ePnEShG4YG9o3uC6IAUDKyebIrweg06R9mhjn7tAUpOaQ9pywqChaEgIaBDcMRmc150drKd9VF6TkUGg+SgoA1Gv5embcQQBAritjlyyxyQGA67N1qlp+6RKbHIAPwlyC80E4ecaNx9olNjkAUH93Nyeo4LoKToJ+i3E+FgAY98ZmCYRS6LebFyI+CHMCNQjThKadPyRksgAA47J8pUCoqb9llmaJKQAwLstrEKQ7XyfUvMl3gbVZvlIcnlqTWO9mn8ZqxYFqZpwW7+QGH0W2BJAagvYwtGsAPghzZeuRX6GUZLmzAgDGl85zMybqeCKz/L9AhX7Xh2tXGGyMmlouAIAO4TeAE4Aul5Cp5QQA9NvjGvTb4qb2gX6fYHYIuXeJtegd/eu0v6AHU2TWY7JNboTwx2l/GX475BRjtU+wRR8OH057h8xhYLlRskM/E0YIJnnAerv8COEKoySYexXYnVnPAHN7ALAWYG0PANYCrO0BwFqAtX0CJHH8H1ebuYYAAAAASUVORK5CYII=' + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAADKUlEQVR4nO2b4XGcMBCFn9OAKeE68JVACddB6CBXgtPBdWCuA3cQ0gHu4NIBqeD5BzDmlF0hkNAyk9sZZjw6Cd5+aFcLyE8k8T/bN2sB1vYAYC3A2h4ArAVY2wOAtQBrswZwAXC0FGAJoAbwA0ADQwhWAM4Avg9/P6OHUFoIsQLg3vFnAL8AVLmFWAGoAFyF9jdkhmCZAyrsAMLWAAoAB8/vFYCfQvsbgNf0cgQjueVRk+xIHmf6VZSt3ljfpgDOE0d2C2GrE58ER3YJYYuTHgdnJesGJ33jS2V8Q7LYO4CC5E1xfmpzEDSIbWoIqQE0gmgNyC4gpHS+FsS+e37bBYRUzlcBItdCKIZzuRaSVLMAKBeIOwt9TSHEOn+gPEVLzxhptphBiHFeE3R2+kniYiA0CgQf9E0AvAtCasH5jnLSWgsBXJ9PkgG4CBdvnT4F78Oj5b+zwRzCGucl0Tfe32EtPC6B5yPJ1xwQljovrctSEgoJjxAIvjHj8aqMDQG4CIA7pUdzaWvhMVe4xEBolGsmBSBNaZdySHikhiDVFsGVYqjztXCRRuhX8n6WuOFxYB8ePnEShG4YG9o3uC6IAUDKyebIrweg06R9mhjn7tAUpOaQ9pywqChaEgIaBDcMRmc150drKd9VF6TkUGg+SgoA1Gv5embcQQBAritjlyyxyQGA67N1qlp+6RKbHIAPwlyC80E4ecaNx9olNjkAUH93Nyeo4LoKToJ+i3E+FgAY98ZmCYRS6LebFyI+CHMCNQjThKadPyRksgAA47J8pUCoqb9llmaJKQAwLstrEKQ7XyfUvMl3gbVZvlIcnlqTWO9mn8ZqxYFqZpwW7+QGH0W2BJAagvYwtGsAPghzZeuRX6GUZLmzAgDGl85zMybqeCKz/L9AhX7Xh2tXGGyMmlouAIAO4TeAE4Aul5Cp5QQA9NvjGvTb4qb2gX6fYHYIuXeJtegd/eu0v6AHU2TWY7JNboTwx2l/GX475BRjtU+wRR8OH057h8xhYLlRskM/E0YIJnnAerv8COEKoySYexXYnVnPAHN7ALAWYG0PANYCrO0BwFqAtX0CJHH8H1ebuYYAAAAASUVORK5CYII="; var DATA_PROCESSING_URI = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAACTUlEQVR4nO2a4U3DMBBGH4gFwggwAoxQRggjlBHKCGWEdoQyQhmhjAAjNCMcP+IK54DWSXw5BH5SRVz5nM8fbmxffCYi/GfOvQV4UwzwFuBNMcBbgDfFAG8B3hQDvAV4M6UBN8AC2AMSrg/Mw3eH72dTibqY6D4r2k6msAx/n4F7GzmfTDECNqR3PqYOsbaIiOVnJl9ZHqm//Kb+zFKjtQEr1ZlFQsxCxWwsNZ6J7Xb4DbiKypdAkxC3B6pw3YQ4E6yfAXHnX0nrPMBLdF2pdrJiaYAWndr5vnVHURZC3gK8KQZ4C/CmGOAtwJtiwIT3GjO3V6erDOO3LoTeT7SVDUsDblRZd+oYuq5uKxtWm6GKdiMUD91r0k3Q8Q1w2yM+GYsRUANbup1f0098AzxF5Sq0WY9Wp8m8v36Tr2wz5hMk3CObZutZYA3cjYh/oDsSsmNtwBzYMXwa29LNHufHIM00E5GdGrZD0lo6P7gTkTq3XstZYEd3/r6lzQqlxu+jckM7i2RPlFj9BBra339Mn5cduu4aoyyR5TPgRZX7rOZ03dSR0xtLA/R/rM+DUBuQfQF0YMrN0JgNjVmStGyHvQV4UwzwFuBNMcBbgDfFAMO29eKlzzrALAmqsT4fMPQ9/585H6Df86ecFarpjgC9p8jKFBmhmBXHExyLUCfG1ACzszfRZyPfE58Xmv9Qx/R8kEyQE4T2rN/zgLg/c04Q2o7cAY+c3tk9hrrmnQf7WeDXUxZC3gK8KQZ4C/CmGOAtwJtigLcAb4oB3gK8+QAub0WlJ923VAAAAABJRU5ErkJggg==' + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsSAAALEgHS3X78AAACTUlEQVR4nO2a4U3DMBBGH4gFwggwAoxQRggjlBHKCGWEdoQyQhmhjAAjNCMcP+IK54DWSXw5BH5SRVz5nM8fbmxffCYi/GfOvQV4UwzwFuBNMcBbgDfFAG8B3hQDvAV4M6UBN8AC2AMSrg/Mw3eH72dTibqY6D4r2k6msAx/n4F7GzmfTDECNqR3PqYOsbaIiOVnJl9ZHqm//Kb+zFKjtQEr1ZlFQsxCxWwsNZ6J7Xb4DbiKypdAkxC3B6pw3YQ4E6yfAXHnX0nrPMBLdF2pdrJiaYAWndr5vnVHURZC3gK8KQZ4C/CmGOAtwJtiwIT3GjO3V6erDOO3LoTeT7SVDUsDblRZd+oYuq5uKxtWm6GKdiMUD91r0k3Q8Q1w2yM+GYsRUANbup1f0098AzxF5Sq0WY9Wp8m8v36Tr2wz5hMk3CObZutZYA3cjYh/oDsSsmNtwBzYMXwa29LNHufHIM00E5GdGrZD0lo6P7gTkTq3XstZYEd3/r6lzQqlxu+jckM7i2RPlFj9BBra339Mn5cduu4aoyyR5TPgRZX7rOZ03dSR0xtLA/R/rM+DUBuQfQF0YMrN0JgNjVmStGyHvQV4UwzwFuBNMcBbgDfFAMO29eKlzzrALAmqsT4fMPQ9/585H6Df86ecFarpjgC9p8jKFBmhmBXHExyLUCfG1ACzszfRZyPfE58Xmv9Qx/R8kEyQE4T2rN/zgLg/c04Q2o7cAY+c3tk9hrrmnQf7WeDXUxZC3gK8KQZ4C/CmGOAtwJtigLcAb4oB3gK8+QAub0WlJ923VAAAAABJRU5ErkJggg=="; class AI { constructor() { @@ -28,7 +28,7 @@ * response: string | null * }>} */ - this.chats = new Map() + this.chats = new Map(); /** * @type {Map} */ - this.presets = new Map() + this.presets = new Map(); } getInfo() { return { - id: 'ai', - color1: '#3a3a3a', - color2: '#444444', - color3: '#777777', - name: Scratch.translate('AI'), + id: "ai", + color1: "#3a3a3a", + color2: "#444444", + color3: "#777777", + name: Scratch.translate("AI"), blocks: [ { - opcode: 'isInternetConnected', + opcode: "isInternetConnected", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Internet connected?'), + text: Scratch.translate("Internet connected?"), blockType: Scratch.BlockType.BOOLEAN, arguments: {}, }, { - opcode: 'createNewChat', + opcode: "createNewChat", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Create a new chat with id[ID]'), + text: Scratch.translate("Create a new chat with id[ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'deleteChat', + opcode: "deleteChat", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Delete chat with id[ID]'), + text: Scratch.translate("Delete chat with id[ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'clearAllChats', + opcode: "clearAllChats", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Clear all chats'), + text: Scratch.translate("Clear all chats"), blockType: Scratch.BlockType.COMMAND, }, { - opcode: 'createNewAIPreset', + opcode: "createNewAIPreset", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Create a new AI preset with id [ID]'), + text: Scratch.translate("Create a new AI preset with id [ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('preset1'), + defaultValue: Scratch.translate("preset1"), }, }, }, { - opcode: 'deletePreset', + opcode: "deletePreset", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Delete AI preset with id [ID]'), + text: Scratch.translate("Delete AI preset with id [ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('preset1'), + defaultValue: Scratch.translate("preset1"), }, }, }, { - opcode: 'clearAllPresets', + opcode: "clearAllPresets", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Clear all AI presets'), + text: Scratch.translate("Clear all AI presets"), blockType: Scratch.BlockType.COMMAND, }, { - opcode: 'setAiPresetProp', + opcode: "setAiPresetProp", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Set the [PROP] of AI preset [ID] to [VALUE]'), + text: Scratch.translate( + "Set the [PROP] of AI preset [ID] to [VALUE]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('preset1'), + defaultValue: Scratch.translate("preset1"), }, PROP: { type: Scratch.ArgumentType.STRING, - menu: 'AI_PRESET_PROP', - defaultValue: 'apikey', + menu: "AI_PRESET_PROP", + defaultValue: "apikey", }, VALUE: { type: Scratch.ArgumentType.STRING, - defaultValue: 'sk-xxxx', + defaultValue: "sk-xxxx", }, }, }, { - opcode: 'assignAiPresetToChat', + opcode: "assignAiPresetToChat", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Use AI preset [PRESET_ID] for chat [CHAT_ID]'), + text: Scratch.translate( + "Use AI preset [PRESET_ID] for chat [CHAT_ID]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { PRESET_ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('preset1'), + defaultValue: Scratch.translate("preset1"), }, CHAT_ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'setRequestFormatOfPreset', + opcode: "setRequestFormatOfPreset", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Set request format for preset with id[ID] to [FORMAT]'), + text: Scratch.translate( + "Set request format for preset with id[ID] to [FORMAT]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('preset1'), + defaultValue: Scratch.translate("preset1"), }, FORMAT: { type: Scratch.ArgumentType.STRING, - menu: 'REQUEST_FORMATS', - defaultValue: 'openai', + menu: "REQUEST_FORMATS", + defaultValue: "openai", }, }, }, { - opcode: 'sendMessage', + opcode: "sendMessage", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Send message [MESSAGE] to chat with id[ID]'), + text: Scratch.translate( + "Send message [MESSAGE] to chat with id[ID]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { MESSAGE: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('Hello'), + defaultValue: Scratch.translate("Hello"), }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'stopMessage', + opcode: "stopMessage", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Stop message from chat [ID]'), + text: Scratch.translate("Stop message from chat [ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'response', + opcode: "response", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Response from chat [ID]'), + text: Scratch.translate("Response from chat [ID]"), blockType: Scratch.BlockType.REPORTER, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'status', + opcode: "status", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Status of chat with id[ID]'), + text: Scratch.translate("Status of chat with id[ID]"), blockType: Scratch.BlockType.REPORTER, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'isIdle', + opcode: "isIdle", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('Is chat with id[ID] idle?'), + text: Scratch.translate("Is chat with id[ID] idle?"), blockType: Scratch.BlockType.BOOLEAN, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { blockType: Scratch.BlockType.LABEL, - text: Scratch.translate('Common AI'), + text: Scratch.translate("Common AI"), }, { - opcode: 'anthropicUrl', + opcode: "anthropicUrl", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('URL of [AI](Anthropic)'), + text: Scratch.translate("URL of [AI](Anthropic)"), blockType: Scratch.BlockType.REPORTER, arguments: { AI: { type: Scratch.ArgumentType.STRING, - menu: 'COMMON_AI_ANTHROPIC_URLS', - defaultValue: 'https://api.deepseek.com/anthropic', + menu: "COMMON_AI_ANTHROPIC_URLS", + defaultValue: "https://api.deepseek.com/anthropic", }, }, }, { - opcode: 'openaiUrl', + opcode: "openaiUrl", blockIconURI: BLOCK_ICON_URI, - text: Scratch.translate('URL of [AI](OpenAI)'), + text: Scratch.translate("URL of [AI](OpenAI)"), blockType: Scratch.BlockType.REPORTER, arguments: { AI: { type: Scratch.ArgumentType.STRING, - menu: 'COMMON_AI_OPENAI_URLS', - defaultValue: 'https://api.deepseek.com', + menu: "COMMON_AI_OPENAI_URLS", + defaultValue: "https://api.deepseek.com", }, }, }, { blockType: Scratch.BlockType.LABEL, - text: Scratch.translate('Data processing(JSON)'), + text: Scratch.translate("Data processing(JSON)"), }, { - opcode: 'setDataOfMap', + opcode: "setDataOfMap", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Overwrite id[ID] in [MAP] with JSON [DATA]'), + text: Scratch.translate( + "Overwrite id[ID] in [MAP] with JSON [DATA]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: 'chat1', + defaultValue: "chat1", }, MAP: { type: Scratch.ArgumentType.STRING, - menu: 'MAP', - defaultValue: 'chats', + menu: "MAP", + defaultValue: "chats", }, DATA: { type: Scratch.ArgumentType.STRING, - defaultValue: '{}', + defaultValue: "{}", }, }, }, { - opcode: 'setDataOfChatHistory', + opcode: "setDataOfChatHistory", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Overwrite chat history of id[ID] with JSON [DATA]'), + text: Scratch.translate( + "Overwrite chat history of id[ID] with JSON [DATA]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: 'chat1', + defaultValue: "chat1", }, DATA: { type: Scratch.ArgumentType.STRING, - defaultValue: '{}', + defaultValue: "{}", }, }, }, { - opcode: 'clearChatHistory', + opcode: "clearChatHistory", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Clear chat history of id[ID]'), + text: Scratch.translate("Clear chat history of id[ID]"), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: 'chat1', + defaultValue: "chat1", }, }, }, { - opcode: 'addMessageOfChatHistory', + opcode: "addMessageOfChatHistory", blockIconURI: DATA_PROCESSING_URI, text: Scratch.translate( - 'Add a message to chat [ID] with role[ROLE] and content[CONTENT]', + "Add a message to chat [ID] with role[ROLE] and content[CONTENT]" ), blockType: Scratch.BlockType.COMMAND, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: 'chat1', + defaultValue: "chat1", }, ROLE: { type: Scratch.ArgumentType.STRING, - menu: 'CHAT_ROLE', - defaultValue: 'user', + menu: "CHAT_ROLE", + defaultValue: "user", }, CONTENT: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('How do you do?'), + defaultValue: Scratch.translate("How do you do?"), }, }, }, { - opcode: 'deleteItemOfChatHistory', + opcode: "deleteItemOfChatHistory", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Delete message at index [INDEX] from chat with id[ID]'), + text: Scratch.translate( + "Delete message at index [INDEX] from chat with id[ID]" + ), blockType: Scratch.BlockType.COMMAND, arguments: { INDEX: { @@ -351,14 +365,14 @@ }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: 'chat1', + defaultValue: "chat1", }, }, }, { - opcode: 'coverObject', + opcode: "coverObject", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Cover [DIFF] to [BASE]'), + text: Scratch.translate("Cover [DIFF] to [BASE]"), blockType: Scratch.BlockType.REPORTER, arguments: { DIFF: { @@ -367,23 +381,23 @@ }, BASE: { type: Scratch.ArgumentType.STRING, - defaultValue: '{}', + defaultValue: "{}", }, }, }, { - opcode: 'setObjectProp', + opcode: "setObjectProp", blockIconURI: DATA_PROCESSING_URI, text: Scratch.translate("Set [OBJECT]'s [KEY] to [VALUE]"), blockType: Scratch.BlockType.COMMAND, arguments: { OBJECT: { type: Scratch.ArgumentType.STRING, - defaultValue: '{}', + defaultValue: "{}", }, KEY: { type: Scratch.ArgumentType.STRING, - defaultValue: 'key1', + defaultValue: "key1", }, VALUE: { type: Scratch.ArgumentType.STRING, @@ -392,57 +406,59 @@ }, }, { - opcode: 'allKeysOfMap', + opcode: "allKeysOfMap", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('All keys of [MAP]'), + text: Scratch.translate("All keys of [MAP]"), blockType: Scratch.BlockType.REPORTER, arguments: { MAP: { type: Scratch.ArgumentType.STRING, - menu: 'MAP', - defaultValue: 'chats', + menu: "MAP", + defaultValue: "chats", }, }, }, { - opcode: 'dataOfMap', + opcode: "dataOfMap", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Data of [ID] from [MAP]'), + text: Scratch.translate("Data of [ID] from [MAP]"), blockType: Scratch.BlockType.REPORTER, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, MAP: { type: Scratch.ArgumentType.STRING, - menu: 'MAP', - defaultValue: 'chats', + menu: "MAP", + defaultValue: "chats", }, }, }, { - opcode: 'chatHistory', + opcode: "chatHistory", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Chat history from id[ID]'), + text: Scratch.translate("Chat history from id[ID]"), blockType: Scratch.BlockType.REPORTER, arguments: { ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'propOfChatHistory', + opcode: "propOfChatHistory", blockIconURI: DATA_PROCESSING_URI, blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[PROP] of item [INDEX] of chat history id[ID]'), + text: Scratch.translate( + "[PROP] of item [INDEX] of chat history id[ID]" + ), arguments: { PROP: { type: Scratch.ArgumentType.STRING, - menu: 'CHAT_HISTORY_PROP', - defaultValue: 'content', + menu: "CHAT_HISTORY_PROP", + defaultValue: "content", }, INDEX: { type: Scratch.ArgumentType.NUMBER, @@ -450,27 +466,27 @@ }, ID: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('chat1'), + defaultValue: Scratch.translate("chat1"), }, }, }, { - opcode: 'lengthOfArray', + opcode: "lengthOfArray", blockIconURI: DATA_PROCESSING_URI, - text: Scratch.translate('Length of [ARRAY]'), + text: Scratch.translate("Length of [ARRAY]"), blockType: Scratch.BlockType.REPORTER, arguments: { ARRAY: { type: Scratch.ArgumentType.STRING, - defaultValue: '[]', + defaultValue: "[]", }, }, }, { - opcode: 'itemOfArray', + opcode: "itemOfArray", blockIconURI: DATA_PROCESSING_URI, blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('Item [INDEX] of array[ARRAY]'), + text: Scratch.translate("Item [INDEX] of array[ARRAY]"), arguments: { INDEX: { type: Scratch.ArgumentType.NUMBER, @@ -478,23 +494,23 @@ }, ARRAY: { type: Scratch.ArgumentType.STRING, - defaultValue: '[]', + defaultValue: "[]", }, }, }, { - opcode: 'itemOfObject', + opcode: "itemOfObject", blockIconURI: DATA_PROCESSING_URI, blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('Item [KEY] of object[OBJECT]'), + text: Scratch.translate("Item [KEY] of object[OBJECT]"), arguments: { KEY: { type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate('key1'), + defaultValue: Scratch.translate("key1"), }, OBJECT: { type: Scratch.ArgumentType.STRING, - defaultValue: '{}', + defaultValue: "{}", }, }, }, @@ -504,12 +520,12 @@ acceptReporters: true, items: [ { - text: Scratch.translate('Chats'), - value: 'chats', + text: Scratch.translate("Chats"), + value: "chats", }, { - text: Scratch.translate('AI presets'), - value: 'aipresets', + text: Scratch.translate("AI presets"), + value: "aipresets", }, ], }, @@ -517,16 +533,16 @@ acceptReporters: true, items: [ { - text: Scratch.translate('API Key'), - value: 'apikey', + text: Scratch.translate("API Key"), + value: "apikey", }, { - text: Scratch.translate('Request URL'), - value: 'requesturl', + text: Scratch.translate("Request URL"), + value: "requesturl", }, { - text: Scratch.translate('Model'), - value: 'model', + text: Scratch.translate("Model"), + value: "model", }, ], }, @@ -534,12 +550,12 @@ acceptReporters: false, items: [ { - text: 'OpenAI', - value: 'openai', + text: "OpenAI", + value: "openai", }, { - text: 'Anthropic', - value: 'anthropic', + text: "Anthropic", + value: "anthropic", }, ], }, @@ -547,96 +563,97 @@ acceptReporters: false, items: [ { - text: 'OpenAI', - value: 'https://api.openai.com/v1', + text: "OpenAI", + value: "https://api.openai.com/v1", }, { - text: 'DeepSeek', - value: 'https://api.deepseek.com', + text: "DeepSeek", + value: "https://api.deepseek.com", }, { - text: 'Qwen', - value: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + text: "Qwen", + value: "https://dashscope.aliyuncs.com/compatible-mode/v1", }, { - text: 'Kimi', - value: 'https://api.moonshot.cn/v1', + text: "Kimi", + value: "https://api.moonshot.cn/v1", }, { - text: 'GLM', - value: 'https://open.bigmodel.cn/api/paas/v4', + text: "GLM", + value: "https://open.bigmodel.cn/api/paas/v4", }, { - text: 'Baichuan', - value: 'https://api.baichuan-ai.com/v1', + text: "Baichuan", + value: "https://api.baichuan-ai.com/v1", }, { - text: 'Doubao', - value: 'https://ark.cn-beijing.volces.com/api/v3', + text: "Doubao", + value: "https://ark.cn-beijing.volces.com/api/v3", }, { - text: 'MiniMax', - value: 'https://api.minimax.chat/v1', + text: "MiniMax", + value: "https://api.minimax.chat/v1", }, { - text: 'Stepfun', - value: 'https://api.stepfun.com/v1', + text: "Stepfun", + value: "https://api.stepfun.com/v1", }, { - text: 'Hunyuan', - value: 'https://api.hunyuan.cloud.tencent.com/v1', + text: "Hunyuan", + value: "https://api.hunyuan.cloud.tencent.com/v1", }, { - text: 'Spark', - value: 'https://spark-api-open.xf-yun.com/v1', + text: "Spark", + value: "https://spark-api-open.xf-yun.com/v1", }, { - text: 'Gemini', - value: 'https://generativelanguage.googleapis.com/v1beta/openai', + text: "Gemini", + value: + "https://generativelanguage.googleapis.com/v1beta/openai", }, { - text: 'Groq', - value: 'https://api.groq.com/openai/v1', + text: "Groq", + value: "https://api.groq.com/openai/v1", }, { - text: 'Mistral', - value: 'https://api.mistral.ai/v1', + text: "Mistral", + value: "https://api.mistral.ai/v1", }, { - text: 'Cohere', - value: 'https://api.cohere.ai/compatibility/v1', + text: "Cohere", + value: "https://api.cohere.ai/compatibility/v1", }, { - text: 'Grok', - value: 'https://api.x.ai/v1', + text: "Grok", + value: "https://api.x.ai/v1", }, { - text: 'OpenRouter', - value: 'https://openrouter.ai/api/v1', + text: "OpenRouter", + value: "https://openrouter.ai/api/v1", }, { - text: 'Together AI', - value: 'https://api.together.xyz/v1', + text: "Together AI", + value: "https://api.together.xyz/v1", }, { - text: 'Fireworks AI', - value: 'https://api.fireworks.ai/inference/v1', + text: "Fireworks AI", + value: "https://api.fireworks.ai/inference/v1", }, { - text: 'Perplexity', - value: 'https://api.perplexity.ai', + text: "Perplexity", + value: "https://api.perplexity.ai", }, { - text: 'Novita AI', - value: 'https://api.novita.ai/v3/openai', + text: "Novita AI", + value: "https://api.novita.ai/v3/openai", }, { - text: 'SiliconFlow', - value: 'https://api.siliconflow.cn/v1', + text: "SiliconFlow", + value: "https://api.siliconflow.cn/v1", }, { - text: 'Vercel AI Gateway', - value: 'https://ai-gateway.vercel.sh/v1', + text: "Vercel AI Gateway", + value: "https://ai-gateway.vercel.sh/v1", }, ], }, @@ -645,61 +662,62 @@ acceptReporters: false, items: [ { - text: 'DeepSeek', - value: 'https://api.deepseek.com/anthropic', + text: "DeepSeek", + value: "https://api.deepseek.com/anthropic", }, { - text: 'Grok', - value: 'https://api.x.ai', + text: "Grok", + value: "https://api.x.ai", }, { - text: 'GLM ', - value: 'https://open.bigmodel.cn/api/anthropic', + text: "GLM ", + value: "https://open.bigmodel.cn/api/anthropic", }, { - text: 'Qwen', - value: 'https://dashscope.aliyuncs.com/apps/anthropic', + text: "Qwen", + value: "https://dashscope.aliyuncs.com/apps/anthropic", }, { - text: 'Mimo', - value: 'https://token-plan-cn.xiaomimimo.com', + text: "Mimo", + value: "https://token-plan-cn.xiaomimimo.com", }, { - text: 'Gemini', - value: 'https://generativelanguage.googleapis.com/v1beta/openai', + text: "Gemini", + value: + "https://generativelanguage.googleapis.com/v1beta/openai", }, { - text: 'MiniMax', - value: 'https://api.minimaxi.com/anthropic', + text: "MiniMax", + value: "https://api.minimaxi.com/anthropic", }, { - text: 'Seed Code', - value: 'https://ark.cn-beijing.volces.com/api/compatible', + text: "Seed Code", + value: "https://ark.cn-beijing.volces.com/api/compatible", }, { - text: 'Kimi', - value: 'https://api.moonshot.cn', + text: "Kimi", + value: "https://api.moonshot.cn", }, { - text: 'Claude', - value: 'https://api.anthropic.com', + text: "Claude", + value: "https://api.anthropic.com", }, { - text: 'OpenRouter', - value: 'https://openrouter.ai/api', + text: "OpenRouter", + value: "https://openrouter.ai/api", }, { - text: 'OfoxAI', - value: 'https://api.ofox.io/anthropic', + text: "OfoxAI", + value: "https://api.ofox.io/anthropic", }, { - text: 'Vercel', - value: 'https://ai-gateway.vercel.sh', + text: "Vercel", + value: "https://ai-gateway.vercel.sh", }, { - text: 'Cloudflare AI', + text: "Cloudflare AI", value: - 'https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY_ID/anthropic', + "https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY_ID/anthropic", }, ], }, @@ -712,12 +730,12 @@ acceptReporters: true, items: [ { - text: Scratch.translate('role'), - value: 'role', + text: Scratch.translate("role"), + value: "role", }, { - text: Scratch.translate('content'), - value: 'content', + text: Scratch.translate("content"), + value: "content", }, ], }, @@ -725,191 +743,194 @@ acceptReporters: false, items: [ { - text: Scratch.translate('user'), - value: 'user', + text: Scratch.translate("user"), + value: "user", }, { - text: Scratch.translate('assistant'), - value: 'assistant', + text: Scratch.translate("assistant"), + value: "assistant", }, ], }, }, - } + }; } /** * @param {string} url * @param {"anthropic"|"openai"} format * @returns {string} */ - _padUrl(url, format = 'anthropic') { - if (typeof url !== 'string') return '' - var result = url - while (result.endsWith('/')) { - result = result.slice(0, -1) + _padUrl(url, format = "anthropic") { + if (typeof url !== "string") return ""; + var result = url; + while (result.endsWith("/")) { + result = result.slice(0, -1); } switch (format) { - case 'anthropic': - if (result.endsWith('/v1')) { - result += '/messages' - } else if (!result.endsWith('/v1/messages')) { - result += '/v1/messages' + case "anthropic": + if (result.endsWith("/v1")) { + result += "/messages"; + } else if (!result.endsWith("/v1/messages")) { + result += "/v1/messages"; } - break - case 'openai': + break; + case "openai": // OpenAI-compatible APIs expect the path to end with /v1/chat/completions - if (!result.endsWith('/v1/chat/completions')) { - if (result.endsWith('/v1')) { - result += '/chat/completions' + if (!result.endsWith("/v1/chat/completions")) { + if (result.endsWith("/v1")) { + result += "/chat/completions"; } else { - result += '/v1/chat/completions' + result += "/v1/chat/completions"; } } - break + break; default: - break + break; } - return result + return result; } _sendAnthropic(chat, requesturl, headers, body) { function fetchHandle(response) { chat.messages.push({ - role: 'assistant', - content: '', - }) + role: "assistant", + content: "", + }); if (!response.ok) { - chat.messages[chat.messages.length - 1].content = 'Request failed:' + response.status - chat.status = 'idle' - return + chat.messages[chat.messages.length - 1].content = + "Request failed:" + response.status; + chat.status = "idle"; + return; } - var reader = response.body.getReader() - var decoder = new TextDecoder() - var buffer = '' + var reader = response.body.getReader(); + var decoder = new TextDecoder(); + var buffer = ""; function readHandle(result) { if (result.done) { - chat.messages[chat.messages.length - 1].content = chat.response - chat.status = 'idle' - chat.response = null - return + chat.messages[chat.messages.length - 1].content = chat.response; + chat.status = "idle"; + chat.response = null; + return; } - buffer += decoder.decode(result.value, { stream: true }) - var lines = buffer.split('\n') - buffer = lines.pop() || '' + buffer += decoder.decode(result.value, { stream: true }); + var lines = buffer.split("\n"); + buffer = lines.pop() || ""; for (var i = 0; i < lines.length; i++) { - var line = lines[i] - if (!line.startsWith('data: ')) continue - var jsonStr = line.slice(6) + var line = lines[i]; + if (!line.startsWith("data: ")) continue; + var jsonStr = line.slice(6); try { - var data = JSON.parse(jsonStr) + var data = JSON.parse(jsonStr); } catch (e) { - continue + continue; } switch (data.type) { - case 'content_block_start': - break - case 'content_block_delta': + case "content_block_start": + break; + case "content_block_delta": if (data.delta && data.delta.text) { - chat.response += data.delta.text - chat.messages[chat.messages.length - 1].content = chat.response + chat.response += data.delta.text; + chat.messages[chat.messages.length - 1].content = + chat.response; } - break - case 'content_block_stop': - break - case 'message_delta': - break - case 'message_stop': - break - case 'error': - chat.messages[chat.messages.length - 1].content += '[error]' - chat.status = 'idle' - chat.response = null - break + break; + case "content_block_stop": + break; + case "message_delta": + break; + case "message_stop": + break; + case "error": + chat.messages[chat.messages.length - 1].content += "[error]"; + chat.status = "idle"; + chat.response = null; + break; } } - readStream() + readStream(); } function readError(error) { - if (error.name !== 'AbortError') { - chat.messages[chat.messages.length - 1].content += '[break]' + if (error.name !== "AbortError") { + chat.messages[chat.messages.length - 1].content += "[break]"; } else { - chat.messages[chat.messages.length - 1].content = chat.response + chat.messages[chat.messages.length - 1].content = chat.response; } - chat.status = 'idle' - chat.response = null + chat.status = "idle"; + chat.response = null; } function readStream() { - reader.read().then(readHandle).catch(readError) + reader.read().then(readHandle).catch(readError); } - readStream() + readStream(); } function fetchError(error) { - if (error.name === 'AbortError') { - chat.messages.push({ role: 'assistant', content: chat.response }) + if (error.name === "AbortError") { + chat.messages.push({ role: "assistant", content: chat.response }); } else { chat.messages.push({ - role: 'assistant', - content: 'Request failed: ' + error.message, - }) + role: "assistant", + content: "Request failed: " + error.message, + }); } - chat.status = 'idle' - chat.response = null + chat.status = "idle"; + chat.response = null; } Scratch.fetch(requesturl, { - method: 'POST', + method: "POST", headers: headers, body: JSON.stringify(body), signal: chat.controller.signal, }) .then(fetchHandle) - .catch(fetchError) + .catch(fetchError); } // Send a streaming request using the OpenAI-compatible chat completions format. // The structure mirrors _sendAnthropic above: fetch -> read SSE stream -> accumulate deltas. _sendOpenai(chat, requesturl, headers, body) { function fetchHandle(response) { chat.messages.push({ - role: 'assistant', - content: '', - }) + role: "assistant", + content: "", + }); if (!response.ok) { - chat.messages[chat.messages.length - 1].content = 'Request failed:' + response.status - chat.status = 'idle' - return + chat.messages[chat.messages.length - 1].content = + "Request failed:" + response.status; + chat.status = "idle"; + return; } - var reader = response.body.getReader() - var decoder = new TextDecoder() - var buffer = '' + var reader = response.body.getReader(); + var decoder = new TextDecoder(); + var buffer = ""; function readHandle(result) { if (result.done) { - chat.messages[chat.messages.length - 1].content = chat.response - chat.status = 'idle' - chat.response = null - return + chat.messages[chat.messages.length - 1].content = chat.response; + chat.status = "idle"; + chat.response = null; + return; } - buffer += decoder.decode(result.value, { stream: true }) - var lines = buffer.split('\n') - buffer = lines.pop() || '' + buffer += decoder.decode(result.value, { stream: true }); + var lines = buffer.split("\n"); + buffer = lines.pop() || ""; for (var i = 0; i < lines.length; i++) { - var line = lines[i] + var line = lines[i]; // Each SSE event starts with "data: " followed by JSON - if (!line.startsWith('data: ')) continue - var jsonStr = line.slice(6) + if (!line.startsWith("data: ")) continue; + var jsonStr = line.slice(6); // "[DONE]" marker signals the end of the stream - if (jsonStr.trim() === '[DONE]') { + if (jsonStr.trim() === "[DONE]") { // Stream finished normally — final content is already in chat.response - continue + continue; } try { - var data = JSON.parse(jsonStr) + var data = JSON.parse(jsonStr); } catch (e) { - continue + continue; } // OpenAI-style SSE: data.choices[0].delta.content @@ -917,389 +938,389 @@ data.choices && data.choices[0] && data.choices[0].delta && - typeof data.choices[0].delta.content === 'string' + typeof data.choices[0].delta.content === "string" ) { - chat.response += data.choices[0].delta.content - chat.messages[chat.messages.length - 1].content = chat.response + chat.response += data.choices[0].delta.content; + chat.messages[chat.messages.length - 1].content = chat.response; } } - readStream() + readStream(); } function readError(error) { - if (error.name !== 'AbortError') { - chat.messages[chat.messages.length - 1].content += '[break]' + if (error.name !== "AbortError") { + chat.messages[chat.messages.length - 1].content += "[break]"; } else { - chat.messages[chat.messages.length - 1].content = chat.response + chat.messages[chat.messages.length - 1].content = chat.response; } - chat.status = 'idle' - chat.response = null + chat.status = "idle"; + chat.response = null; } function readStream() { - reader.read().then(readHandle).catch(readError) + reader.read().then(readHandle).catch(readError); } - readStream() + readStream(); } function fetchError(error) { - if (error.name === 'AbortError') { - chat.messages.push({ role: 'assistant', content: chat.response }) + if (error.name === "AbortError") { + chat.messages.push({ role: "assistant", content: chat.response }); } else { chat.messages.push({ - role: 'assistant', - content: 'Request failed: ' + error.message, - }) + role: "assistant", + content: "Request failed: " + error.message, + }); } - chat.status = 'idle' - chat.response = null + chat.status = "idle"; + chat.response = null; } Scratch.fetch(requesturl, { - method: 'POST', + method: "POST", headers: headers, body: JSON.stringify(body), signal: chat.controller.signal, }) .then(fetchHandle) - .catch(fetchError) + .catch(fetchError); } isInternetConnected() { - return navigator.onLine || false + return navigator.onLine || false; } deletePreset(args) { - var ID = Scratch.Cast.toString(args.ID) - if (!this.presets.has(ID)) return - this.presets.delete(ID) + var ID = Scratch.Cast.toString(args.ID); + if (!this.presets.has(ID)) return; + this.presets.delete(ID); } clearAllPresets() { - this.presets.clear() + this.presets.clear(); } clearAllChats() { - this.chats.clear() + this.chats.clear(); } deleteChat(args) { - var ID = Scratch.Cast.toString(args.ID) - if (!this.chats.has(ID)) return - this.chats.delete(ID) + var ID = Scratch.Cast.toString(args.ID); + if (!this.chats.has(ID)) return; + this.chats.delete(ID); } setRequestFormatOfPreset(args) { - var ID = Scratch.Cast.toString(args.ID) - var FORMAT = Scratch.Cast.toString(args.FORMAT) - if (!this.presets.has(ID)) return - this.presets.get(ID).requestformat = FORMAT + var ID = Scratch.Cast.toString(args.ID); + var FORMAT = Scratch.Cast.toString(args.FORMAT); + if (!this.presets.has(ID)) return; + this.presets.get(ID).requestformat = FORMAT; } createNewChat(args) { - var ID = Scratch.Cast.toString(args.ID) - if (this.chats.has(ID)) return + var ID = Scratch.Cast.toString(args.ID); + if (this.chats.has(ID)) return; this.chats.set(ID, { id: ID, - aiPresetId: '', + aiPresetId: "", messages: [], - status: 'idle', + status: "idle", controller: null, response: null, - }) + }); } status(args) { - const ID = Scratch.Cast.toString(args.ID) - if (!this.chats.has(ID)) return '' - return this.chats.get(ID).status + const ID = Scratch.Cast.toString(args.ID); + if (!this.chats.has(ID)) return ""; + return this.chats.get(ID).status; } isIdle(args) { - const ID = Scratch.Cast.toString(args.ID) - if (!this.chats.has(ID)) return false - return this.chats.get(ID).status === 'idle' + const ID = Scratch.Cast.toString(args.ID); + if (!this.chats.has(ID)) return false; + return this.chats.get(ID).status === "idle"; } createNewAIPreset(args) { - var ID = Scratch.Cast.toString(args.ID) - if (this.presets.has(ID)) return + var ID = Scratch.Cast.toString(args.ID); + if (this.presets.has(ID)) return; this.presets.set(ID, { id: ID, - requestformat: 'openai', - requesturl: '', - model: '', - apikey: '', - }) + requestformat: "openai", + requesturl: "", + model: "", + apikey: "", + }); } setAiPresetProp(args) { - var ID = Scratch.Cast.toString(args.ID) - var currentPreset = this.presets.get(ID) - if (!currentPreset) return - var PROP = Scratch.Cast.toString(args.PROP) - var VALUE = Scratch.Cast.toString(args.VALUE) + var ID = Scratch.Cast.toString(args.ID); + var currentPreset = this.presets.get(ID); + if (!currentPreset) return; + var PROP = Scratch.Cast.toString(args.PROP); + var VALUE = Scratch.Cast.toString(args.VALUE); switch (PROP) { - case 'apikey': - currentPreset.apikey = VALUE - break - case 'model': - currentPreset.model = VALUE - break - case 'requesturl': - currentPreset.requesturl = VALUE - break - case 'requestformat': - currentPreset.requestformat = VALUE - break + case "apikey": + currentPreset.apikey = VALUE; + break; + case "model": + currentPreset.model = VALUE; + break; + case "requesturl": + currentPreset.requesturl = VALUE; + break; + case "requestformat": + currentPreset.requestformat = VALUE; + break; default: - return + return; } } assignAiPresetToChat(args) { - var PRESET_ID = Scratch.Cast.toString(args.PRESET_ID) - var CHAT_ID = Scratch.Cast.toString(args.CHAT_ID) - var chat = this.chats.get(CHAT_ID) - if (!chat) return - chat.aiPresetId = PRESET_ID + var PRESET_ID = Scratch.Cast.toString(args.PRESET_ID); + var CHAT_ID = Scratch.Cast.toString(args.CHAT_ID); + var chat = this.chats.get(CHAT_ID); + if (!chat) return; + chat.aiPresetId = PRESET_ID; } sendMessage(args) { - var MESSAGE = Scratch.Cast.toString(args.MESSAGE) - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return - if (chat.status !== 'idle') return - var preset = this.presets.get(chat.aiPresetId) + var MESSAGE = Scratch.Cast.toString(args.MESSAGE); + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return; + if (chat.status !== "idle") return; + var preset = this.presets.get(chat.aiPresetId); if (!preset) { chat.messages.push({ - role: 'assistant', - content: 'Error: No AI preset assigned', - }) - chat.status = 'idle' - return + role: "assistant", + content: "Error: No AI preset assigned", + }); + chat.status = "idle"; + return; } - var format = preset.requestformat - chat.status = 'pending' + var format = preset.requestformat; + chat.status = "pending"; var message = { - role: 'user', + role: "user", content: MESSAGE, - } - chat.controller = new AbortController() - chat.messages.push(message) - chat.response = '' + }; + chat.controller = new AbortController(); + chat.messages.push(message); + chat.response = ""; - var requesturl = this._padUrl(preset.requesturl, format) - var headers - var body + var requesturl = this._padUrl(preset.requesturl, format); + var headers; + var body; - if (format === 'anthropic') { + if (format === "anthropic") { headers = { - 'Content-Type': 'application/json', - 'x-api-key': preset.apikey, - 'anthropic-version': '2023-06-01', - 'anthropic-dangerous-direct-browser-access': 'true', - } + "Content-Type": "application/json", + "x-api-key": preset.apikey, + "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true", + }; body = { model: preset.model, messages: chat.messages.map(function (msg) { - return { role: msg.role, content: msg.content } + return { role: msg.role, content: msg.content }; }), stream: true, max_tokens: 9070, - } - this._sendAnthropic(chat, requesturl, headers, body) - } else if (format === 'openai') { + }; + this._sendAnthropic(chat, requesturl, headers, body); + } else if (format === "openai") { // OpenAI-compatible chat completions format headers = { - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + preset.apikey, - } + "Content-Type": "application/json", + Authorization: "Bearer " + preset.apikey, + }; body = { model: preset.model, messages: chat.messages.map(function (msg) { - return { role: msg.role, content: msg.content } + return { role: msg.role, content: msg.content }; }), stream: true, - } - this._sendOpenai(chat, requesturl, headers, body) + }; + this._sendOpenai(chat, requesturl, headers, body); } } stopMessage(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return - if (chat.controller) chat.controller.abort() + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return; + if (chat.controller) chat.controller.abort(); } response(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return '' - return chat.response || '' + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return ""; + return chat.response || ""; } anthropicUrl(args) { - return Scratch.Cast.toString(args.AI) + return Scratch.Cast.toString(args.AI); } openaiUrl(args) { - return Scratch.Cast.toString(args.AI) + return Scratch.Cast.toString(args.AI); } addMessageOfChatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var ROLE = Scratch.Cast.toString(args.ROLE) - var CONTENT = Scratch.Cast.toString(args.CONTENT) - var chat = this.chats.get(ID) - if (!chat) return - chat.messages.push({ role: ROLE, content: CONTENT }) + var ID = Scratch.Cast.toString(args.ID); + var ROLE = Scratch.Cast.toString(args.ROLE); + var CONTENT = Scratch.Cast.toString(args.CONTENT); + var chat = this.chats.get(ID); + if (!chat) return; + chat.messages.push({ role: ROLE, content: CONTENT }); } deleteItemOfChatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) - if (!chat) return + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)); + if (!chat) return; // Convert 1-based index to 0-based - chat.messages.splice(INDEX - 1, 1) + chat.messages.splice(INDEX - 1, 1); } clearChatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return - chat.messages = [] + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return; + chat.messages = []; } setDataOfChatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return - var data + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return; + var data; try { - data = JSON.parse(Scratch.Cast.toString(args.DATA)) - if (!Array.isArray(data)) return + data = JSON.parse(Scratch.Cast.toString(args.DATA)); + if (!Array.isArray(data)) return; } catch { - return + return; } - chat.messages = data + chat.messages = data; } coverObject(args) { - var base - var diff + var base; + var diff; // In terms of performance, using a for loop directly is better than Object.assign. try { - base = JSON.parse(Scratch.Cast.toString(args.BASE)) - diff = JSON.parse(Scratch.Cast.toString(args.DIFF)) + base = JSON.parse(Scratch.Cast.toString(args.BASE)); + diff = JSON.parse(Scratch.Cast.toString(args.DIFF)); for (var i in diff) { // In this situation, there's no need to worry about using Object.prototype.hasOwnProperty.call. - base[i] = diff[i] + base[i] = diff[i]; } } catch { - return '{}' + return "{}"; } - return JSON.stringify(base) + return JSON.stringify(base); } setObjectProp(args) { - var obj - var prop - var value + var obj; + var prop; + var value; try { - obj = JSON.parse(Scratch.Cast.toString(args.OBJECT)) - prop = Scratch.Cast.toString(args.KEY) - value = JSON.parse(Scratch.Cast.toString(args.VALUE)) - obj[prop] = value + obj = JSON.parse(Scratch.Cast.toString(args.OBJECT)); + prop = Scratch.Cast.toString(args.KEY); + value = JSON.parse(Scratch.Cast.toString(args.VALUE)); + obj[prop] = value; } catch { - return '{}' + return "{}"; } - return JSON.stringify(obj) + return JSON.stringify(obj); } allKeysOfMap(args) { - var MAP = Scratch.Cast.toString(args.MAP) + var MAP = Scratch.Cast.toString(args.MAP); switch (MAP) { - case 'chats': - return JSON.stringify(Array.from(this.chats.keys())) - case 'aipresets': - return JSON.stringify(Array.from(this.presets.keys())) + case "chats": + return JSON.stringify(Array.from(this.chats.keys())); + case "aipresets": + return JSON.stringify(Array.from(this.presets.keys())); default: - return '[]' + return "[]"; } } dataOfMap(args) { - var ID = Scratch.Cast.toString(args.ID) - var MAP = Scratch.Cast.toString(args.MAP) - var currentMap + var ID = Scratch.Cast.toString(args.ID); + var MAP = Scratch.Cast.toString(args.MAP); + var currentMap; switch (MAP) { - case 'chats': - currentMap = this.chats.get(ID) - break - case 'aipresets': - currentMap = this.presets.get(ID) - break + case "chats": + currentMap = this.chats.get(ID); + break; + case "aipresets": + currentMap = this.presets.get(ID); + break; default: - return '{}' + return "{}"; } - if (!currentMap) return '{}' - return JSON.stringify(currentMap) + if (!currentMap) return "{}"; + return JSON.stringify(currentMap); } setDataOfMap(args) { - var ID = Scratch.Cast.toString(args.ID) - var MAP = Scratch.Cast.toString(args.MAP) - var DATA = Scratch.Cast.toString(args.DATA) - var currentMap - var parsedData + var ID = Scratch.Cast.toString(args.ID); + var MAP = Scratch.Cast.toString(args.MAP); + var DATA = Scratch.Cast.toString(args.DATA); + var currentMap; + var parsedData; switch (MAP) { - case 'chats': - currentMap = this.chats - break - case 'aipresets': - currentMap = this.presets - break + case "chats": + currentMap = this.chats; + break; + case "aipresets": + currentMap = this.presets; + break; default: - return + return; } try { - parsedData = JSON.parse(DATA) + parsedData = JSON.parse(DATA); } catch { - return + return; } - if (!(typeof parsedData === 'object')) return - if (!Array.isArray(parsedData)) return - currentMap.set(ID, parsedData) + if (!(typeof parsedData === "object")) return; + if (!Array.isArray(parsedData)) return; + currentMap.set(ID, parsedData); } chatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var chat = this.chats.get(ID) - if (!chat) return '[]' - return JSON.stringify(chat.messages) + var ID = Scratch.Cast.toString(args.ID); + var chat = this.chats.get(ID); + if (!chat) return "[]"; + return JSON.stringify(chat.messages); } propOfChatHistory(args) { - var ID = Scratch.Cast.toString(args.ID) - var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) - var PROP = Scratch.Cast.toString(args.PROP) - var chat = this.chats.get(ID) - if (!chat) return '' - if (INDEX < 1 || INDEX > chat.messages.length) return '' - var msg = chat.messages[INDEX - 1] - return msg[PROP] || '' + var ID = Scratch.Cast.toString(args.ID); + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)); + var PROP = Scratch.Cast.toString(args.PROP); + var chat = this.chats.get(ID); + if (!chat) return ""; + if (INDEX < 1 || INDEX > chat.messages.length) return ""; + var msg = chat.messages[INDEX - 1]; + return msg[PROP] || ""; } lengthOfArray(args) { - var ARRAY = Scratch.Cast.toString(args.ARRAY) - var parsedArray + var ARRAY = Scratch.Cast.toString(args.ARRAY); + var parsedArray; try { - parsedArray = JSON.parse(ARRAY) + parsedArray = JSON.parse(ARRAY); } catch { - parsedArray = [] + parsedArray = []; } - return parsedArray.length + return parsedArray.length; } itemOfArray(args) { - var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)) - var ARRAY = Scratch.Cast.toString(args.ARRAY) - var parsedArray + var INDEX = parseInt(Scratch.Cast.toNumber(args.INDEX)); + var ARRAY = Scratch.Cast.toString(args.ARRAY); + var parsedArray; try { - parsedArray = JSON.parse(ARRAY) + parsedArray = JSON.parse(ARRAY); } catch { - parsedArray = [] + parsedArray = []; } - var item = parsedArray[INDEX - 1] - if (typeof item === 'undefined') return '' - return item + var item = parsedArray[INDEX - 1]; + if (typeof item === "undefined") return ""; + return item; } itemOfObject(args) { - var KEY = Scratch.Cast.toString(args.KEY) - var OBJECT = Scratch.Cast.toString(args.OBJECT) - var parsedObject + var KEY = Scratch.Cast.toString(args.KEY); + var OBJECT = Scratch.Cast.toString(args.OBJECT); + var parsedObject; try { - parsedObject = JSON.parse(OBJECT) + parsedObject = JSON.parse(OBJECT); } catch { - parsedObject = {} + parsedObject = {}; } - var item = parsedObject[KEY] - if (typeof item === 'undefined') return '' - return item + var item = parsedObject[KEY]; + if (typeof item === "undefined") return ""; + return item; } } - Scratch.extensions.register(new AI()) -})(Scratch) + Scratch.extensions.register(new AI()); +})(Scratch);