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);