From d78a66ca8b3b43a2bacc45a721cdffec06751a72 Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:43:00 +0800 Subject: [PATCH 1/7] =?UTF-8?q?Delete=20image/=E5=AF=BB=E8=B7=AF.jpg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "image/\345\257\273\350\267\257.jpg" | Bin 30480 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 "image/\345\257\273\350\267\257.jpg" diff --git "a/image/\345\257\273\350\267\257.jpg" "b/image/\345\257\273\350\267\257.jpg" deleted file mode 100644 index d980523f0d180f2a49940fe1604b46a5adeffdcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30480 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%0DGZGIO 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 KUk#>zOaBeBV)do~ From 3486602c3860ae8938e7cd8b70bde577a84ee7e8 Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:43:57 +0800 Subject: [PATCH 2/7] Add files via upload --- image/download.svg | 138 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 image/download.svg diff --git a/image/download.svg b/image/download.svg new file mode 100644 index 0000000..f23644b --- /dev/null +++ b/image/download.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 22d4106918524a5e4e8a72accef05544ba35de3f Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:44:21 +0800 Subject: [PATCH 3/7] Add files via upload --- download.svg | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 download.svg diff --git a/download.svg b/download.svg new file mode 100644 index 0000000..f23644b --- /dev/null +++ b/download.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5ca0e031aabc6b245c37e4b27c4ef5f171654673 Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:44:57 +0800 Subject: [PATCH 4/7] =?UTF-8?q?Rename=20download.svg=20to=20=E5=AF=BB?= =?UTF-8?q?=E8=B7=AF.svg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- image/download.svg => "image/\345\257\273\350\267\257.svg" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename image/download.svg => "image/\345\257\273\350\267\257.svg" (100%) diff --git a/image/download.svg "b/image/\345\257\273\350\267\257.svg" similarity index 100% rename from image/download.svg rename to "image/\345\257\273\350\267\257.svg" From 2f16cfb1207218dc00d2e7e82e41334d4578b054 Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:46:11 +0800 Subject: [PATCH 5/7] Delete download.svg --- download.svg | 138 --------------------------------------------------- 1 file changed, 138 deletions(-) delete mode 100644 download.svg diff --git a/download.svg b/download.svg deleted file mode 100644 index f23644b..0000000 --- a/download.svg +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 9669fe0f09b3df7d632bc262cd5eb12ae617b31e Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:46:53 +0800 Subject: [PATCH 6/7] Update extensions.json --- extensions.json | 278 ++++++++++++++++++++++++------------------------ 1 file changed, 141 insertions(+), 137 deletions(-) diff --git a/extensions.json b/extensions.json index 7913140..cad63e3 100644 --- a/extensions.json +++ b/extensions.json @@ -1,9 +1,9 @@ { "extensions": [ - { - "slug": "kylin", - "id": "kylin", - "name": "kylin", + { + "slug": "kylin", + "id": "kylin", + "name": "kylin", "description": "Kylin is the first-ever obfuscator for Scratch (Turbowarp) that enables you to encrypt your project, preventing it from being stolen or hacked.", "descriptionTranslations": { "zh-cn": "Kylin 是业界第一个用于 Scratch (Turbowarp) 的混淆器,允许你 加密 你的项目,来避免它被盗或被破解。", @@ -46,38 +46,38 @@ ], "docs": false }, - { - "slug": "transformers", - "id": "huggingfacetransformers", - "name": "Hugging Face Transformers", - "description": "Run Hugging Face Transformers.js models in TurboWarp with configurable tasks, devices, and dtypes.", - "image": "transformers.svg", - "by": [ - { - "name": "0.2Studio" - } - ], - "docs": false - }, - { - "slug": "spinepro", - "id": "spinePro", - "name": "SpinePro", - "description": "Use Spine skeletal animation in Scratch projects.", - "image": "spinepro.svg", - "by": [ - { - "name": "PPN-design", - "link": "https://github.com/DDguan2010" - } - ], - "docs": false - }, - { - "slug": "cybertoolbox", - "id": "toolbox", - "name": "Cybertoolbox", - "description": "Put a bunch of things together!", + { + "slug": "transformers", + "id": "huggingfacetransformers", + "name": "Hugging Face Transformers", + "description": "Run Hugging Face Transformers.js models in TurboWarp with configurable tasks, devices, and dtypes.", + "image": "transformers.svg", + "by": [ + { + "name": "0.2Studio" + } + ], + "docs": false + }, + { + "slug": "spinepro", + "id": "spinePro", + "name": "SpinePro", + "description": "Use Spine skeletal animation in Scratch projects.", + "image": "spinepro.svg", + "by": [ + { + "name": "PPN-design", + "link": "https://github.com/DDguan2010" + } + ], + "docs": false + }, + { + "slug": "cybertoolbox", + "id": "toolbox", + "name": "Cybertoolbox", + "description": "Put a bunch of things together!", "nameTranslations": { "zh-cn": "赛博猫猫的工具箱", "en": "Cyberexplorer's ToolBox" @@ -798,25 +798,29 @@ ], "docs": false }, - { + { "slug": "寻路", "id": "寻路", "name": "寻路", - "description": "寻路。", + "description": "一个高性能的寻路扩展", "nameTranslations": { - "zh-cn": "寻路" + "zh-cn": "一个高性能的寻路扩展" + }, + "descriptionTranslations": { + "zh-cn": "一个高性能的寻路扩展" }, - "image": "寻路.jpg", + "image": "寻路.svg", "by": [ { - "name": "yuan", - "link": "https://www.ccw.site/student/687f6ba9fc898317568cdc8d" + "name": "Arkos(使兼容)", + "link": "https://space.bilibili.com/446532545?spm_id_from=333.337.0.0" + }, + { + "name": "朱元翔", + "link": "https://space.bilibili.com/3546902484224452?spm_id_from=333.1387.0.0" } ], - "docs": true, - "samples": [ - "寻路" - ] + "docs": false, }, { "slug": "musicplus", @@ -840,97 +844,97 @@ ], "docs": false }, - { - "slug": "调试器", - "id": "DebuggerExtensionTS", - "name": "调试器", - "description": "调试器", - "nameTranslations": { - "zh-cn": "调试器" - }, - "descriptionTranslations": { - "zh-cn": "调试器" - }, - "image": "调试器.png", - "by": [ - { - "name": "TheShovle" - }, - { - "name": "yuan", - "link": "https://www.ccw.site/student/687f6ba9fc898317568cdc8d" - } - ], - "scratchCompatible": false, - "docs": false - }, - { - "slug": "panel", - "id": "settingspanel", - "name": "panel", - "description": "panel", - "nameTranslations": { - "zh-cn": "panel" - }, - "descriptionTranslations": { - "zh-cn": "panel" - }, - "image": "panel.svg", - "by": [ - { - "name": "yuan", - "link": "https://www.ccw.site/student/687f6ba9fc898317568cdc8d" - } - ], - "scratchCompatible": false, - "docs": false - }, - { - "slug": "mdui", - "id": "mdui", - "name": "mdui弹窗", - "description": "A dialog and snackbar extension based on mdui for building interactive UI in projects.", - "nameTranslations": { - "zh-cn": "mdui弹窗", - "en": "mdui Dialog" - }, - "descriptionTranslations": { - "zh-cn": "基于 mdui 的弹窗与消息提示扩展,可用于在作品中构建交互式界面。", - "en": "A dialog and snackbar extension based on mdui for building interactive UI in projects." - }, - "image": "mdui.jpeg", - "by": [ - { - "name": "不想上学" - } - ], - "scratchCompatible": false, - "docs": false - }, - { - "slug": "cbeg", - "id": "cbeg", - "name": "CB Extension Gallery", - "description": "A lightweight tool to build Scratch extensions and open the CB Extension Gallery.", - "nameTranslations": { - "zh-cn": "CB Extension Gallery", - "en": "CB Extension Gallery" - }, - "descriptionTranslations": { - "zh-cn": "一个轻量的 Scratch 扩展制作工具,并可直接打开 CB Extension Gallery。", - "en": "A lightweight tool to build Scratch extensions and open the CB Extension Gallery." - }, - "image": "cbeg.png", - "by": [ - { - "name": "ChessBrainIsNotHuman", - "link": "https://chessbrain.qzz.io/" - } - ], - "docs": false - } - ] -} + { + "slug": "调试器", + "id": "DebuggerExtensionTS", + "name": "调试器", + "description": "调试器", + "nameTranslations": { + "zh-cn": "调试器" + }, + "descriptionTranslations": { + "zh-cn": "调试器" + }, + "image": "调试器.png", + "by": [ + { + "name": "TheShovle" + }, + { + "name": "yuan", + "link": "https://www.ccw.site/student/687f6ba9fc898317568cdc8d" + } + ], + "scratchCompatible": false, + "docs": false + }, + { + "slug": "panel", + "id": "settingspanel", + "name": "panel", + "description": "panel", + "nameTranslations": { + "zh-cn": "panel" + }, + "descriptionTranslations": { + "zh-cn": "panel" + }, + "image": "panel.svg", + "by": [ + { + "name": "yuan", + "link": "https://www.ccw.site/student/687f6ba9fc898317568cdc8d" + } + ], + "scratchCompatible": false, + "docs": false + }, + { + "slug": "mdui", + "id": "mdui", + "name": "mdui弹窗", + "description": "A dialog and snackbar extension based on mdui for building interactive UI in projects.", + "nameTranslations": { + "zh-cn": "mdui弹窗", + "en": "mdui Dialog" + }, + "descriptionTranslations": { + "zh-cn": "基于 mdui 的弹窗与消息提示扩展,可用于在作品中构建交互式界面。", + "en": "A dialog and snackbar extension based on mdui for building interactive UI in projects." + }, + "image": "mdui.jpeg", + "by": [ + { + "name": "不想上学" + } + ], + "scratchCompatible": false, + "docs": false + }, + { + "slug": "cbeg", + "id": "cbeg", + "name": "CB Extension Gallery", + "description": "A lightweight tool to build Scratch extensions and open the CB Extension Gallery.", + "nameTranslations": { + "zh-cn": "CB Extension Gallery", + "en": "CB Extension Gallery" + }, + "descriptionTranslations": { + "zh-cn": "一个轻量的 Scratch 扩展制作工具,并可直接打开 CB Extension Gallery。", + "en": "A lightweight tool to build Scratch extensions and open the CB Extension Gallery." + }, + "image": "cbeg.png", + "by": [ + { + "name": "ChessBrainIsNotHuman", + "link": "https://chessbrain.qzz.io/" + } + ], + "docs": false + } + ] +} From 8147e820abcd329014ef358f79a09d8d0ffc1cc0 Mon Sep 17 00:00:00 2001 From: 13244431027 <13244431027@163.com> Date: Sat, 18 Jul 2026 18:51:43 +0800 Subject: [PATCH 7/7] =?UTF-8?q?Update=20github=E9=9D=A2=E6=9D=BF.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "extension/github\351\235\242\346\235\277.js" | 1527 +++-------------- 1 file changed, 265 insertions(+), 1262 deletions(-) diff --git "a/extension/github\351\235\242\346\235\277.js" "b/extension/github\351\235\242\346\235\277.js" index fb9c5d2..6af1b11 100644 --- "a/extension/github\351\235\242\346\235\277.js" +++ "b/extension/github\351\235\242\346\235\277.js" @@ -1,3 +1,10 @@ +// Name: GitHub 面板 +// ID: githubpanel +// Description: 利用githubAPI互交 +// By: yuan +// License: MIT + + // ==================== 模块定义 ==================== // 工具模块 @@ -103,7 +110,7 @@ class Utils { let html = ''; html += ''; headers.forEach(h => html += ``); - html += ''; + html += ``; rows.forEach(row => { html += ''; row.forEach((cell, i) => { @@ -129,90 +136,6 @@ class Utils { text = text.replace(/\n/g, '
'); return text; } - - static createScrollbarStyle(target, thumb = 'rgba(255,255,255,0.25)', track = 'rgba(255,255,255,0.05)') { - if (!target) return; - target.style.scrollbarWidth = 'thin'; - target.style.scrollbarColor = `${thumb} ${track}`; - } - - static ensureStyle(id, css) { - if (document.getElementById(id)) return; - const style = document.createElement('style'); - style.id = id; - style.textContent = css; - document.head.appendChild(style); - } - - static parsePluginSettingsSchema(schema) { - if (!Array.isArray(schema)) return []; - const out = []; - for (const item of schema) { - if (typeof item !== 'string') continue; - const raw = item.trim(); - if (!raw) continue; - - // string:a - let m = raw.match(/^string\s*:\s*([a-zA-Z_]\w*)$/i); - if (m) { - out.push({ - type: 'string', - key: m[1], - label: m[1], - default: '' - }); - continue; - } - - // count:b / number:b - m = raw.match(/^(count|number)\s*:\s*([a-zA-Z_]\w*)$/i); - if (m) { - out.push({ - type: 'number', - key: m[2], - label: m[2], - default: 0 - }); - continue; - } - - // slider(1-100):c / silder(1-100):c - m = raw.match(/^(slider|silder)\s*\(\s*(-?\d+(?:\.\d+)?)\s*-\s*(-?\d+(?:\.\d+)?)\s*\)\s*:\s*([a-zA-Z_]\w*)$/i); - if (m) { - out.push({ - type: 'slider', - key: m[4], - label: m[4], - min: Number(m[2]), - max: Number(m[3]), - step: 1, - default: Number(m[2]) - }); - continue; - } - - // choose(1,2,3):d - m = raw.match(/^choose\s*\(\s*([^)]+)\)\s*:\s*([a-zA-Z_]\w*)$/i); - if (m) { - const vals = m[1].split(',').map(s => s.trim()).filter(Boolean); - out.push({ - type: 'choose', - key: m[2], - label: m[2], - options: vals, - default: vals[0] ?? '' - }); - continue; - } - - // 兼容对象字符串 JSON - try { - const obj = JSON.parse(raw); - if (obj && obj.key && obj.type) out.push(obj); - } catch {} - } - return out; - } } // 错误处理模块 @@ -364,17 +287,6 @@ class APIManager { } } - async fetchText(url, headers = {}) { - const res = await fetch(url, { - headers: { - ...headers, - ...this.headers - } - }); - if (!res.ok) throw new Error(`请求失败: ${res.status}`); - return await res.text(); - } - async fetchBlob(owner, repo, sha) { const url = `https://api.github.com/repos/${owner}/${repo}/git/blobs/${sha}`; const res = await fetch(url, { @@ -600,22 +512,6 @@ class AIManager { stream: true } }; - } else if (this.provider === 'siliconflow') { - if (!this.config.siliconKey) throw new Error('硅基流动 Key 未设置'); - return { - url: 'https://api.siliconflow.cn/v1/chat/completions', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.siliconKey}` - }, - body: { - model: this.config.siliconModel, - messages: messages, - stream: true, - temperature: this.config.siliconParams.temperature, - max_tokens: this.config.siliconParams.maxTokens - } - }; } else if (this.provider === 'custom') { if (!this.config.customAI.url || !this.config.customAI.key) { throw new Error('自定义 AI URL 或 Key 未设置'); @@ -635,71 +531,6 @@ class AIManager { } throw new Error('未知的AI提供商'); } - - async translate(text, model) { - if (!this.config.siliconKey) throw new Error('硅基流动 Key 未设置'); - - const messages = [{ - role: 'system', - content: 'You are a professional technical translator. Translate the following code or text to Simplified Chinese. Preserve logic and variable names where appropriate. Only output the translation.' - }, { - role: 'user', - content: text.slice(0, 3000) + (text.length > 3000 ? '\n\n(Truncated)' : '') - }]; - - const res = await fetch('https://api.siliconflow.cn/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.siliconKey}` - }, - body: JSON.stringify({ - model: model, - messages: messages, - stream: false - }) - }); - - if (!res.ok) { - const errText = await res.text(); - throw new Error(`翻译API错误 (${res.status}): ${errText}`); - } - - const data = await res.json(); - if (data.choices && data.choices[0]) { - return data.choices[0].message.content; - } - throw new Error('翻译返回格式错误'); - } - - async generateIntro(repoName, desc, model) { - if (!this.config.siliconKey) throw new Error('硅基流动 Key 未设置'); - - const prompt = `请简要介绍 GitHub 仓库 "${repoName}"。描述: "${desc || ''}"。请用中文总结它的主要功能和用途。`; - const res = await fetch('https://api.siliconflow.cn/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.config.siliconKey}` - }, - body: JSON.stringify({ - model: model, - messages: [{ - role: 'user', - content: prompt - }], - stream: false - }) - }); - - if (!res.ok) throw new Error(`Intro API错误: ${res.status}`); - - const data = await res.json(); - if (data.choices && data.choices[0]) { - return data.choices[0].message.content; - } - throw new Error('Intro返回格式错误'); - } } // 虚拟滚动模块 (仅用于固定高度列表,如文件树、日志) @@ -719,7 +550,6 @@ class VirtualScroller { this.viewport.style.overflowY = 'auto'; this.viewport.style.height = '100%'; this.viewport.style.position = 'relative'; - Utils.createScrollbarStyle(this.viewport); this.content = document.createElement('div'); this.content.style.position = 'relative'; @@ -728,15 +558,14 @@ class VirtualScroller { this.viewport.appendChild(this.content); this.container.appendChild(this.viewport); - this._boundOnScroll = () => this._onScroll(); - this.viewport.addEventListener('scroll', this._boundOnScroll); + this.viewport.addEventListener('scroll', () => this._onScroll()); } setItems(items, renderFn) { this.items = items; this.renderFn = renderFn; this._updateDimensions(); - this._render(true); + this._render(); } _updateDimensions() { @@ -749,14 +578,14 @@ class VirtualScroller { this._render(); } - _render(force = false) { - const viewportHeight = this.viewport.clientHeight || this.container.clientHeight || 300; + _render() { + const viewportHeight = this.viewport.clientHeight; const scrollTop = this.scrollTop; const start = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer); const end = Math.min(this.items.length, Math.ceil((scrollTop + viewportHeight) / this.itemHeight) + this.buffer); - if (!force && start === this.startIndex && end === this.endIndex) return; + if (start === this.startIndex && end === this.endIndex) return; this.startIndex = start; this.endIndex = end; @@ -784,7 +613,7 @@ class VirtualScroller { } destroy() { - this.viewport.removeEventListener('scroll', this._boundOnScroll); + this.viewport.removeEventListener('scroll', this._onScroll); this.container.innerHTML = ''; } } @@ -810,10 +639,7 @@ class UIComponents { boxSizing: 'border-box', padding: '10px', overflow: 'hidden', - boxShadow: '0 8px 32px rgba(0,0,0,0.5)', - resize: 'vertical', - minHeight: '420px', - maxHeight: '95vh' + boxShadow: '0 8px 32px rgba(0,0,0,0.5)' }); return panel; } @@ -886,10 +712,8 @@ class UIComponents { border: '1px solid rgba(255,255,255,0.15)', borderRadius: '10px', background: 'rgba(0,0,0,0.2)', - padding: '0', - minHeight: '0' + padding: '0' }); - Utils.createScrollbarStyle(main); return main; } @@ -934,8 +758,6 @@ class UIComponents { overflow:hidden; color:#fff; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; - resize: vertical; - min-height: 360px; } .pmk2-titlebar{ display:flex; @@ -997,8 +819,6 @@ class UIComponents { display:flex; flex-direction:column; gap: 10px; - overflow:auto; - scrollbar-width: thin; } .pmk2-right{ flex:1; @@ -1007,8 +827,6 @@ class UIComponents { display:flex; flex-direction:column; gap: 10px; - overflow:auto; - scrollbar-width: thin; } .pmk2-field label{ display:block; @@ -1049,7 +867,6 @@ class UIComponents { min-height:0; overflow:auto; padding-right: 4px; - scrollbar-width: thin; } .pmk2-item{ border: 1px solid rgba(255,255,255,0.10); @@ -1104,74 +921,21 @@ class UIComponents { background: rgba(0,0,0,0.25); opacity: 0.85; } - -/* plugin panel scroll */ -.gpp-scrollable{ - overflow:auto !important; - scrollbar-width: thin; -} -.gpp-scrollable::-webkit-scrollbar, -.pmk2-list::-webkit-scrollbar, -.pmk2-left::-webkit-scrollbar, -.pmk2-right::-webkit-scrollbar{ - width:10px; - height:10px; -} -.gpp-scrollable::-webkit-scrollbar-thumb, -.pmk2-list::-webkit-scrollbar-thumb, -.pmk2-left::-webkit-scrollbar-thumb, -.pmk2-right::-webkit-scrollbar-thumb{ - background: rgba(255,255,255,0.25); - border-radius: 8px; -} -.gpp-scrollable::-webkit-scrollbar-track, -.pmk2-list::-webkit-scrollbar-track, -.pmk2-left::-webkit-scrollbar-track, -.pmk2-right::-webkit-scrollbar-track{ - background: rgba(255,255,255,0.05); -} - -/* plugin settings */ -.gpp-plugin-card{ - border:1px solid rgba(255,255,255,0.12); - background: rgba(255,255,255,0.03); - border-radius: 10px; - padding: 10px; - margin-bottom: 10px; -} -.gpp-plugin-settings{ - margin-top:10px; - border-top:1px solid rgba(255,255,255,0.08); - padding-top:10px; - display:flex; - flex-direction:column; - gap:8px; -} -.gpp-plugin-setting-row{ - display:flex; - flex-direction:column; - gap:4px; -} -.gpp-plugin-setting-row label{ - font-size:11px; - opacity:0.75; -} -.gpp-plugin-setting-row input, -.gpp-plugin-setting-row select{ - background: rgba(0,0,0,0.3); - border: 1px solid #555; - color: #fff; - padding: 6px; - border-radius: 6px; -} -.gpp-plugin-setting-inline{ - display:flex; - align-items:center; - gap:8px; +.pmk2-tag{ + display: inline-block; + background: rgba(60,160,255,0.2); + border-radius: 12px; + padding: 2px 8px; + font-size: 10px; + margin-right: 6px; + margin-top: 6px; + border: 1px solid rgba(60,160,255,0.3); } -.gpp-plugin-setting-inline span{ - font-size:12px; - min-width:50px; +.pmk2-description{ + font-size: 12px; + opacity: 0.85; + margin-top: 6px; + line-height: 1.4; } `; document.head.appendChild(style); @@ -1184,220 +948,18 @@ class PluginManager { this.extension = extension; this.plugins = new Map(); this.hooks = new Map(); - this.pluginSettings = new Map(); this._loadFromStorage(); } get context() { - const ext = this.extension; - const core = ext.core; - const api = core.apiManager; - const manager = this; - - const pluginAPI = { - // 基础 - version: '2.0.0', - alert: (msg) => alert(msg), - confirm: (msg) => confirm(msg), - prompt: (msg, def = '') => prompt(msg, def), - log: (...args) => console.log('[PluginAPI]', ...args), - - // UI / 核心 - showPanel: () => ext.showPanel(), - hidePanel: () => ext.hidePanel(), - switchMode: (mode) => ext._switchMode(mode), - getMode: () => core.mode, - setStatus: (msg) => LoadingManager.setMessage(msg), - setError: (msg) => LoadingManager.setError(msg), - getUI: () => ext.ui, - getCore: () => core, - getExtension: () => ext, - - // 仓库上下文 - getCurrentRepo: () => ({ - owner: core.currentOwner, - repo: core.currentRepo, - path: core.currentPath, - branch: core.currentBranch, - defaultBranch: core.defaultBranch - }), - enterRepo: async (owner, repo) => { - ext.ui.ownerInput.value = owner; - ext.ui.repoInput.value = repo; - ext._switchMode('browse'); - await ext._refreshFromInputs(); - }, - loadDir: async (path = '') => await ext.loadDir(path), - openFileByPath: async (path) => { - ErrorHandler.assertRepo(core.currentOwner, core.currentRepo); - const file = await api.fetchJson(`https://api.github.com/repos/${core.currentOwner}/${core.currentRepo}/contents/${path}?ref=${core.currentBranch}`); - return await ext.openFile(file); - }, - - // GitHub API 扩展 - github: { - fetchJson: async (url) => await api.fetchJson(url), - fetchText: async (url, headers = {}) => await api.fetchText(url, headers), - request: async (url, init = {}) => { - const res = await fetch(url, { - ...init, - headers: { - ...(init.headers || {}), - ...api.headers - } - }); - return res; - }, - getRepo: async (owner, repo) => await api.fetchJson(`https://api.github.com/repos/${owner}/${repo}`), - getBranches: async (owner, repo) => await api.fetchJson(`https://api.github.com/repos/${owner}/${repo}/branches`), - getContents: async (owner, repo, path = '', ref = '') => { - const suffix = ref ? `?ref=${encodeURIComponent(ref)}` : ''; - return await api.fetchJson(`https://api.github.com/repos/${owner}/${repo}/contents/${path}${suffix}`); - }, - getCommits: async (owner, repo, query = '') => await api.fetchJson(`https://api.github.com/repos/${owner}/${repo}/commits${query ? (query.startsWith('?') ? query : '?' + query) : ''}`), - putFile: async (...args) => await api.putFile(...args), - deleteFile: async (...args) => await api.deleteFile(...args), - triggerWorkflow: async (...args) => await api.triggerWorkflow(...args), - mergeBranch: async (...args) => await api.mergeBranch(...args), - forkRepo: async (...args) => await api.forkRepo(...args), - createPullRequest: async (...args) => await api.createPullRequest(...args), - headers: () => ({ ...api.headers }) - }, - - // AI 扩展 - ai: { - stream: async (messages, onChunk) => await core.aiManager.stream(messages, onChunk), - abort: () => core.aiManager.abort(), - translate: async (text, model) => await core.aiManager.translate(text, model || core.sfTranslateModel), - generateIntro: async (repoName, desc, model) => await core.aiManager.generateIntro(repoName, desc, model || 'internlm/internlm2_5-7b-chat'), - buffer: () => core.aiManager.streamBuffer, - isStreaming: () => core.aiManager.isStreaming - }, - - // 缓存 - cache: { - get: (key) => core.cacheManager.get(key), - set: (key, value) => core.cacheManager.set(key, value), - clearRepoCache: () => core.cacheManager.clearRepoCache(), - buildRepoContext: (maxChars = 120000) => core.cacheManager.buildContext(maxChars), - getRepoTextCache: () => core.cacheManager.repoTextCache, - getRepoSelection: () => core.cacheManager.repoCacheSelection, - getRepoMeta: () => core.cacheManager.repoCacheMeta - }, - - // 插件系统 - plugins: { - list: () => Array.from(manager.plugins.values()).map(p => ({ - id: p.id, - name: p.name, - version: p.version - })), - get: (id) => manager.plugins.get(id), - unload: (id) => manager.unloadPlugin(id), - trigger: (hook, data) => manager.trigger(hook, data), - importFromGitHub: async (url) => await manager.importFromGitHub(url), - loadCode: async (code, id = null, save = true) => await manager.loadPlugin(code, id, save) - }, - - // 参数系统 - settings: { - getAll: (pluginId) => manager.getPluginSettings(pluginId), - get: (pluginId, key, def = null) => manager.getPluginSetting(pluginId, key, def), - set: (pluginId, key, value) => manager.setPluginSetting(pluginId, key, value), - ownAll: (pluginId) => manager.getPluginSettings(pluginId), - ownGet: (pluginId, key, def = null) => manager.getPluginSetting(pluginId, key, def) - }, - - // 一般工具 - utils: Utils, - components: UIComponents, - createButton: (text, onclick, style = {}) => { - const btn = UIComponents.createWindowButton(text, style); - if (onclick) btn.onclick = onclick; - return btn; - }, - createPanelCard: (title, html = '') => { - const d = document.createElement('div'); - d.className = 'gpp-plugin-card'; - d.innerHTML = `
${title}
${html}`; - return d; - }, - createModal: (title = '插件窗口') => { - const overlay = document.createElement('div'); - Object.assign(overlay.style, { - position: 'fixed', - inset: '0', - background: 'rgba(0,0,0,0.7)', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - zIndex: 100300 - }); - const card = document.createElement('div'); - Object.assign(card.style, { - width: '680px', - maxWidth: '95vw', - maxHeight: '85vh', - overflow: 'auto', - background: '#1e1e1e', - color: '#fff', - border: '1px solid rgba(255,255,255,0.14)', - borderRadius: '12px', - padding: '14px' - }); - Utils.createScrollbarStyle(card); - const header = document.createElement('div'); - Object.assign(header.style, { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: '10px' - }); - const t = document.createElement('div'); - t.textContent = title; - t.style.fontWeight = '800'; - const close = UIComponents.createWindowButton('关闭'); - close.onclick = () => overlay.remove(); - header.appendChild(t); - header.appendChild(close); - const body = document.createElement('div'); - overlay.appendChild(card); - card.appendChild(header); - card.appendChild(body); - overlay.addEventListener('mousedown', (e) => { - if (e.target === overlay) overlay.remove(); - }); - document.body.appendChild(overlay); - return { - overlay, - card, - body, - close: () => overlay.remove() - }; - }, - - // 扩展页面注入 - addTabButton: (text, onClick) => { - const btn = UIComponents.createTabButton(text); - btn.onclick = onClick; - ext.ui.tabs.appendChild(btn); - return btn; - }, - addMainAreaNode: (node) => { - ext.ui.mainArea.appendChild(node); - return node; - } - }; - return { - core, - ui: ext.ui, - api, + core: this.extension.core, + ui: this.extension.ui, + api: this.extension.core.apiManager, utils: Utils, components: UIComponents, - extension: ext, - manager: this, - pluginAPI + extension: this.extension, + manager: this }; } @@ -1415,11 +977,6 @@ class PluginManager { this.extension.core.token = data.token; this.extension.core.updateAIConfig(); } - if (data.pluginSettings) { - Object.entries(data.pluginSettings).forEach(([id, vals]) => { - this.pluginSettings.set(id, vals || {}); - }); - } } } catch (e) { console.error('Failed to load from storage', e); @@ -1435,71 +992,13 @@ class PluginManager { enabled: true }); }); - const settingsObj = {}; - this.pluginSettings.forEach((v, k) => { - settingsObj[k] = v; - }); const data = { token: this.extension.core.token, - plugins: list, - pluginSettings: settingsObj + plugins: list }; localStorage.setItem('github_panel_storage', JSON.stringify(data)); } - _normalizePluginAPI(plugin) { - const schemaRaw = plugin.API || plugin.api || plugin.params || plugin.settingsSchema || []; - const schema = Utils.parsePluginSettingsSchema(schemaRaw); - plugin._settingsSchema = schema; - - if (!this.pluginSettings.has(plugin.id)) this.pluginSettings.set(plugin.id, {}); - const values = this.pluginSettings.get(plugin.id); - - schema.forEach(item => { - if (!(item.key in values)) { - values[item.key] = item.default; - } - }); - - plugin.getSetting = (key, def = null) => this.getPluginSetting(plugin.id, key, def); - plugin.setSetting = (key, value) => this.setPluginSetting(plugin.id, key, value); - plugin.getSettings = () => this.getPluginSettings(plugin.id); - - this.pluginSettings.set(plugin.id, values); - } - - getPluginSettings(id) { - return { - ...(this.pluginSettings.get(id) || {}) - }; - } - - getPluginSetting(id, key, def = null) { - const data = this.pluginSettings.get(id) || {}; - return key in data ? data[key] : def; - } - - setPluginSetting(id, key, value) { - const data = this.pluginSettings.get(id) || {}; - data[key] = value; - this.pluginSettings.set(id, data); - this._saveToStorage(); - this.trigger('plugin:settings:change', { - pluginId: id, - key, - value, - values: { ...data } - }); - const plugin = this.plugins.get(id); - if (plugin && typeof plugin.onSettingsChange === 'function') { - try { - plugin.onSettingsChange(key, value, { ...data }, this.context); - } catch (e) { - console.error(`Plugin ${id} settings change error:`, e); - } - } - } - async loadPlugin(code, id = null, save = true) { if (save) { const warnMsg = "【安全警告】\n将加载外部插件!\n插件 API 系统已 100% 暴露内部核心权限 (Token, 网络请求, 弹窗控制等)。\n继续加载未知插件可能导致数据损坏或隐私泄露。\n出现问题概不负责!\n\n确定要继续加载吗?"; @@ -1508,16 +1007,16 @@ class PluginManager { try { const pluginFactory = new Function('context', ` - const { core, ui, api, utils, components, extension, manager, pluginAPI } = context; + const { core, ui, api, utils, components, extension, manager } = context; const plugin = { id: "${id || 'temp-' + Date.now()}", name: "Unknown Plugin", version: "0.0.1", + description: "", + tags: [], init: () => {}, onHook: () => {}, - onSettingsChange: null, - style: "", - API: [] + style: "" }; ${code} return plugin; @@ -1534,8 +1033,6 @@ class PluginManager { plugin._styleEl = style; } - this._normalizePluginAPI(plugin); - if (plugin.init) plugin.init(this.context); plugin.code = code; @@ -1563,7 +1060,7 @@ class PluginManager { this.plugins.forEach(p => { if (p.onHook) { try { - p.onHook(hookName, data, this.context); + p.onHook(hookName, data); } catch (e) { console.error(`Plugin ${p.id} hook error:`, e); } @@ -1599,19 +1096,11 @@ class GitHubPanelCore { constructor() { this.token = ''; this.aiProvider = 'github'; - this.siliconKey = ''; - this.siliconModel = 'deepseek-ai/DeepSeek-R1-0528-Qwen3-8B'; - this.siliconParams = { - temperature: 0.7, - maxTokens: 4096 - }; this.customAI = { url: 'https://api.openai.com/v1/chat/completions', key: '', model: 'gpt-3.5-turbo' }; - this.sfTranslateEnabled = false; - this.sfTranslateModel = 'internlm/internlm2_5-7b-chat'; this.githubModel = 'gpt-4o'; this.currentOwner = ''; @@ -1647,7 +1136,8 @@ class GitHubPanelCore { branch: "main", dir: "me/github/插件", items: [], - lastLoadedAt: 0 + lastLoadedAt: 0, + searchKeyword: "" }; this.cacheManager = new CacheManager(); @@ -1662,9 +1152,6 @@ class GitHubPanelCore { token: this.token, aiProvider: this.aiProvider, githubModel: this.githubModel, - siliconKey: this.siliconKey, - siliconModel: this.siliconModel, - siliconParams: this.siliconParams, customAI: this.customAI }; } @@ -1685,16 +1172,6 @@ class GitHubPanelCore { this.updateAIConfig(); } - setSiliconKey(key) { - this.siliconKey = key; - this.updateAIConfig(); - } - - setSiliconTranslator(state, model) { - this.sfTranslateEnabled = state === 'on'; - this.sfTranslateModel = model; - } - _revokeObjectUrl() { if (this._objectUrlToRevoke) { URL.revokeObjectURL(this._objectUrlToRevoke); @@ -1714,12 +1191,14 @@ class GitHubPanelExtension { this.virtualScrollers = new Map(); this.pluginManager = new PluginManager(this); this.core.pluginManager = this.pluginManager; + // 自动加载补丁插件(非阻塞) + this._autoLoadPatchPlugin().catch(e => console.error('自动加载补丁插件失败:', e)); } getInfo() { return { id: 'githubpanel', - name: 'GitHub 面板 Pro+', + name: 'GitHub 面板', blocks: [{ opcode: 'showPanel', blockType: Scratch.BlockType.COMMAND, @@ -1749,32 +1228,6 @@ class GitHubPanelExtension { defaultValue: 'github' } } - }, { - opcode: 'setSiliconKey', - blockType: Scratch.BlockType.COMMAND, - text: '硅基流动 Key 为 [KEY]', - arguments: { - KEY: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, { - opcode: 'setSiliconTranslator', - blockType: Scratch.BlockType.COMMAND, - text: '硅基翻译插件 [STATE] 模型 [MODEL]', - arguments: { - STATE: { - type: Scratch.ArgumentType.STRING, - menu: 'onOff', - defaultValue: 'off' - }, - MODEL: { - type: Scratch.ArgumentType.STRING, - menu: 'sfModels', - defaultValue: 'internlm/internlm2_5-7b-chat' - } - } }], menus: { aiProviders: { @@ -1782,45 +1235,10 @@ class GitHubPanelExtension { items: [{ text: 'GitHub', value: 'github' - }, { - text: '硅基流动', - value: 'siliconflow' }, { text: '自定义', value: 'custom' }] - }, - onOff: { - acceptReporters: true, - items: [{ - text: '开启', - value: 'on' - }, { - text: '关闭', - value: 'off' - }] - }, - sfModels: { - acceptReporters: true, - items: [{ - text: 'InternLM2.5-7B', - value: 'internlm/internlm2_5-7b-chat' - }, { - text: 'Hunyuan-MT-7B', - value: 'tencent/Hunyuan-MT-7B' - }, { - text: 'GLM-4-9B', - value: 'THUDM/glm-4-9b-chat' - }, { - text: 'Qwen2.5-7B', - value: 'Qwen/Qwen2.5-7B-Instruct' - }, { - text: 'Qwen2.5-Coder', - value: 'Qwen/Qwen2.5-Coder-7B-Instruct' - }, { - text: 'DeepSeek-OCR', - value: 'deepseek-ai/DeepSeek-OCR' - }] } } }; @@ -1839,8 +1257,6 @@ class GitHubPanelExtension { this.core._revokeObjectUrl(); this.core.aiManager.abort(); this.core._repoCacheAbort = true; - if (this.ui.contextMenu) this.ui.contextMenu.style.display = 'none'; - if (this.ui.aiRewritePanel) this.ui.aiRewritePanel.style.display = 'none'; if (this.ui.panel) this.ui.panel.style.display = 'none'; this._closeMarketplace(); this.virtualScrollers.forEach(vs => vs.destroy()); @@ -1857,13 +1273,53 @@ class GitHubPanelExtension { this.core.setAIProvider(args.PROVIDER); } - setSiliconKey(args) { - this.core.setSiliconKey(String(args.KEY || '').trim()); - } + // ==================== 补丁插件自动加载 ==================== + async _autoLoadPatchPlugin() { + // 防止重复加载 + if (this._patchLoaded) return; + this._patchLoaded = true; + + const PATCH_URL = 'https://raw.githubusercontent.com/13244431027/3/refs/heads/main/me/github/%E6%8F%92%E4%BB%B6/V4%E8%A1%A5%E4%B8%81.js'; - setSiliconTranslator(args) { - this.core.setSiliconTranslator(args.STATE, args.MODEL); - LoadingManager.setMessage(`硅基翻译: ${this.core.sfTranslateEnabled ? '开' : '关'} (${this.core.sfTranslateModel})`); + try { + // 1. 提示用户是否加载补丁插件 + const userConfirmed = confirm( + '检测到可用补丁插件 (V4补丁)\n\n' + + '该插件用于修复已知问题或增强功能。\n' + + '加载插件存在安全风险,请确保来源可信。\n\n' + + '是否确定加载?' + ); + if (!userConfirmed) return; + + // 2. 获取插件代码 + LoadingManager.setMessage('正在获取补丁插件...'); + const response = await fetch(PATCH_URL); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const code = await response.text(); + + // 3. 提取插件 ID(用于卸载旧版本) + let pluginId = null; + const idMatch = code.match(/plugin\.id\s*=\s*["']([^"']+)["']/); + if (idMatch) pluginId = idMatch[1]; + else console.warn('无法从补丁代码中提取 plugin.id,将跳过卸载步骤'); + + // 4. 如果已存在同 ID 插件,先卸载 + if (pluginId && this.pluginManager.plugins.has(pluginId)) { + console.log(`卸载旧版补丁插件: ${pluginId}`); + this.pluginManager.unloadPlugin(pluginId); + // 等待一小段时间确保卸载完成(样式移除等) + await new Promise(r => setTimeout(r, 100)); + } + + // 5. 加载新插件(复用插件管理器的加载逻辑,会弹出安全警告) + LoadingManager.setMessage('正在加载补丁插件...'); + await this.pluginManager.loadPlugin(code, null, true); + LoadingManager.setMessage('补丁插件加载成功'); + console.log('补丁插件已自动加载'); + } catch (error) { + console.error('自动加载补丁插件失败:', error); + LoadingManager.setError(`补丁插件加载失败: ${error.message}`); + } } // ==================== UI创建 ==================== @@ -1941,13 +1397,6 @@ class GitHubPanelExtension { }); window.addEventListener('mouseup', () => drag.on = false); - const resizeObserver = new ResizeObserver(() => { - this.virtualScrollers.forEach(vs => { - try { vs._render(true); } catch {} - }); - }); - resizeObserver.observe(this.ui.panel); - this.ui.tabs = UIComponents.createTabs(); this.ui.tabSearchBtn = UIComponents.createTabButton('搜索', true); this.ui.tabBrowseBtn = UIComponents.createTabButton('浏览'); @@ -1993,15 +1442,13 @@ class GitHubPanelExtension { LoadingManager.init(this.ui.statusLabel); this.ui.mainArea = UIComponents.createMainArea(); - this.ui.mainArea.classList.add('gpp-scrollable'); const contentWrap = document.createElement('div'); Object.assign(contentWrap.style, { display: 'flex', flexDirection: 'column', height: '100%', - width: '100%', - minHeight: '0' + width: '100%' }); const bodyWrap = document.createElement('div'); @@ -2009,8 +1456,7 @@ class GitHubPanelExtension { display: 'flex', flexDirection: 'column', flex: '1', - overflow: 'hidden', - minHeight: '0' + overflow: 'hidden' }); bodyWrap.appendChild(this.ui.tabs); @@ -2036,7 +1482,7 @@ class GitHubPanelExtension { this.pluginManager.trigger('ui:ready', this.ui); } - // ==================== Marketplace Logic Integration ==================== + // ==================== Marketplace Logic Integration (Enhanced) ==================== _createMarketplaceUI() { if (this.ui.marketplace && this.ui.marketplace.overlay) return; @@ -2093,12 +1539,26 @@ class GitHubPanelExtension { const fBranch = this._createMarketField("Branch", mkState.branch); const fDir = this._createMarketField("目录 Path", mkState.dir); + // 新增搜索框 + const searchField = document.createElement("div"); + searchField.className = "pmk2-field"; + const searchLabel = document.createElement("label"); + searchLabel.textContent = "🔍 搜索插件"; + const searchInput = document.createElement("input"); + searchInput.className = "pmk2-input"; + searchInput.placeholder = "按名称、简介或标签搜索..."; + searchInput.value = mkState.searchKeyword || ""; + searchInput.addEventListener("input", (e) => { + this.core.marketplaceState.searchKeyword = e.target.value; + this._renderMarketList(); + }); + searchField.appendChild(searchLabel); + searchField.appendChild(searchInput); + const hint = document.createElement("div"); hint.className = "pmk2-hint"; hint.innerHTML = ` - 规则:只加载 .js,并且忽略 README.md
- 安装时会调用扩展自带的“加载插件”流程(会弹出安全确认)。
- 如果仓库不是公开的,或分支不对,会读取失败。 + 安装时会调用扩展自带的"加载插件"流程(会弹出安全确认)。 `; const status = document.createElement("div"); @@ -2117,6 +1577,7 @@ class GitHubPanelExtension { left.appendChild(fRepo.wrap); left.appendChild(fBranch.wrap); left.appendChild(fDir.wrap); + left.appendChild(searchField); left.appendChild(hint); left.appendChild(status); left.appendChild(btnSaveCfg); @@ -2143,7 +1604,8 @@ class GitHubPanelExtension { owner: fOwner.input, repo: fRepo.input, branch: fBranch.input, - dir: fDir.input + dir: fDir.input, + search: searchInput } }; @@ -2285,17 +1747,92 @@ class GitHubPanelExtension { }) .sort((a, b) => String(a.name).localeCompare(String(b.name))); - this.core.marketplaceState.items = jsFiles; + // 基础 items 列表,稍后增强元数据 + const baseItems = jsFiles.map(file => ({ + ...file, + metadata: { + description: "", + tags: [] + } + })); + + this.core.marketplaceState.items = baseItems; this.core.marketplaceState.lastLoadedAt = Date.now(); + // 先渲染空元数据列表 this._renderMarketList(); - this._setMarketplaceStatus(`已加载:${jsFiles.length} 个 .js(已忽略 README.md)`); + + // 异步加载每个插件的元数据 + await this._enhanceItemsWithMetadata(baseItems, owner, repo, branch); + + this._renderMarketList(); + this._setMarketplaceStatus(`已加载:${baseItems.length} 个 .js(已忽略 README.md)`); } catch (e) { this._renderMarketError(e); this._setMarketplaceStatus(`加载失败:${e.message || e}`); } } + // 提取插件元数据 + _extractPluginMetadata(code) { + let description = ""; + let tags = []; + + // 提取 plugin.description + const descMatch = code.match(/plugin\.description\s*=\s*["']([^"']*)["']/); + if (descMatch) { + description = descMatch[1]; + } + + // 提取 plugin.tags = ["tag1", "tag2", ...] + const tagsMatch = code.match(/plugin\.tags\s*=\s*\[(.*?)\]/s); + if (tagsMatch) { + const tagsStr = tagsMatch[1]; + const tagRegex = /["']([^"']*)["']/g; + let tagMatch; + while ((tagMatch = tagRegex.exec(tagsStr)) !== null) { + tags.push(tagMatch[1]); + } + } + + return { description, tags }; + } + + async _enhanceItemsWithMetadata(items, owner, repo, branch) { + const maxConcurrent = 3; + let index = 0; + + const processNext = async () => { + while (index < items.length) { + const i = index++; + const item = items[i]; + const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${item.path}`; + + try { + const res = await fetch(rawUrl); + if (res.ok) { + const code = await res.text(); + const metadata = this._extractPluginMetadata(code); + item.metadata = metadata; + } + } catch (e) { + console.warn(`Failed to fetch metadata for ${item.name}:`, e); + } + + // 每获取一个就刷新一次显示,让用户看到进度 + if (i % 3 === 0 || i === items.length - 1) { + this._renderMarketList(); + } + } + }; + + const workers = []; + for (let i = 0; i < maxConcurrent; i++) { + workers.push(processNext()); + } + await Promise.all(workers); + } + _renderMarketListLoading() { const list = this.ui.marketplace.list; if (!list) return; @@ -2324,11 +1861,24 @@ class GitHubPanelExtension { repo, branch } = this.core.marketplaceState; - const items = this.core.marketplaceState.items || []; + let items = this.core.marketplaceState.items || []; + + // 应用搜索过滤 + const searchKeyword = (this.core.marketplaceState.searchKeyword || "").toLowerCase().trim(); + if (searchKeyword) { + items = items.filter(item => { + const name = (item.name || "").toLowerCase(); + const description = (item.metadata?.description || "").toLowerCase(); + const tags = (item.metadata?.tags || []).some(tag => tag.toLowerCase().includes(searchKeyword)); + return name.includes(searchKeyword) || description.includes(searchKeyword) || tags; + }); + } + list.innerHTML = ""; if (items.length === 0) { - list.innerHTML = `
目录中没有可用的 .js 插件文件。
`; + const msg = searchKeyword ? `没有找到匹配 "${searchKeyword}" 的插件。` : "目录中没有可用的 .js 插件文件。"; + list.innerHTML = `
${msg}
`; return; } @@ -2352,6 +1902,27 @@ class GitHubPanelExtension { textPart.appendChild(name); textPart.appendChild(meta); + + // 添加简介 + if (it.metadata && it.metadata.description) { + const descDiv = document.createElement("div"); + descDiv.className = "pmk2-description"; + descDiv.textContent = it.metadata.description; + textPart.appendChild(descDiv); + } + + // 添加标签 + if (it.metadata && it.metadata.tags && it.metadata.tags.length > 0) { + const tagsDiv = document.createElement("div"); + tagsDiv.style.marginTop = "6px"; + it.metadata.tags.forEach(tag => { + const tagSpan = document.createElement("span"); + tagSpan.className = "pmk2-tag"; + tagSpan.textContent = tag; + tagsDiv.appendChild(tagSpan); + }); + textPart.appendChild(tagsDiv); + } const btns = document.createElement("div"); btns.className = "pmk2-itembtns"; @@ -2370,7 +1941,6 @@ class GitHubPanelExtension { try { await this.pluginManager.importFromGitHub(rawUrl); alert(`已安装:${it.name}`); - this._refreshPluginsList(); } catch (e) { alert(`安装失败:${e.message || e}`); } finally { @@ -2406,24 +1976,13 @@ class GitHubPanelExtension { _createPluginsArea() { const area = document.createElement('div'); area.style.display = 'none'; - area.style.maxHeight = '260px'; - area.style.overflowY = 'auto'; - area.style.paddingRight = '4px'; - area.classList.add('gpp-scrollable'); - Utils.createScrollbarStyle(area); const controls = document.createElement('div'); Object.assign(controls.style, { display: 'flex', gap: '8px', marginBottom: '10px', - flexWrap: 'wrap', - position: 'sticky', - top: '0', - zIndex: '2', - background: 'rgba(0,0,0,0.45)', - paddingBottom: '8px', - backdropFilter: 'blur(6px)' + flexWrap: 'wrap' }); const importLocalBtn = UIComponents.createWindowButton('导入本地插件 (.js)'); @@ -2479,7 +2038,7 @@ class GitHubPanelExtension { const pasteTextarea = document.createElement('textarea'); Object.assign(pasteTextarea.style, { width: '100%', - height: '120px', + height: '100px', background: 'rgba(0,0,0,0.3)', color: '#fff', border: '1px solid #555', @@ -2487,15 +2046,8 @@ class GitHubPanelExtension { padding: '8px', boxSizing: 'border-box', fontFamily: 'monospace', - fontSize: '12px', - resize: 'vertical', - minHeight: '100px' + fontSize: '12px' }); - const apiHint = document.createElement('div'); - apiHint.style.fontSize = '11px'; - apiHint.style.opacity = '0.65'; - apiHint.innerHTML = `插件参数写法示例:
plugin.API = ["string:a","count:b","silder(1-100):c","choose(1,2,3):d"];
说明:a=字符串,b=数值,c=滑块,d=选择项。`; - const loadPasteBtn = UIComponents.createWindowButton('加载粘贴的代码', { background: '#28a745' }); @@ -2513,7 +2065,6 @@ class GitHubPanelExtension { }; pasteBox.appendChild(pasteLabel); pasteBox.appendChild(pasteTextarea); - pasteBox.appendChild(apiHint); pasteBox.appendChild(loadPasteBtn); area.appendChild(pasteBox); @@ -2523,95 +2074,14 @@ class GitHubPanelExtension { borderRadius: '8px', padding: '10px', background: 'rgba(0,0,0,0.2)', - minHeight: '100px', - maxHeight: '520px', - overflowY: 'auto' + minHeight: '100px' }); - list.classList.add('gpp-scrollable'); this.ui.pluginsList = list; area.appendChild(list); return area; } - _renderPluginSettings(plugin, parent) { - const schema = plugin._settingsSchema || []; - if (!schema.length) return; - - const settingsWrap = document.createElement('div'); - settingsWrap.className = 'gpp-plugin-settings'; - - const title = document.createElement('div'); - title.style.fontWeight = '700'; - title.style.fontSize = '12px'; - title.style.opacity = '0.9'; - title.textContent = '参数设置'; - settingsWrap.appendChild(title); - - const values = this.pluginManager.getPluginSettings(plugin.id); - - schema.forEach(item => { - const row = document.createElement('div'); - row.className = 'gpp-plugin-setting-row'; - - const label = document.createElement('label'); - label.textContent = `${item.label || item.key} (${item.type})`; - row.appendChild(label); - - if (item.type === 'string') { - const inp = document.createElement('input'); - inp.type = 'text'; - inp.value = values[item.key] ?? item.default ?? ''; - inp.onchange = () => this.pluginManager.setPluginSetting(plugin.id, item.key, inp.value); - row.appendChild(inp); - } else if (item.type === 'number') { - const inp = document.createElement('input'); - inp.type = 'number'; - inp.value = values[item.key] ?? item.default ?? 0; - inp.onchange = () => this.pluginManager.setPluginSetting(plugin.id, item.key, Number(inp.value || 0)); - row.appendChild(inp); - } else if (item.type === 'slider') { - const box = document.createElement('div'); - box.className = 'gpp-plugin-setting-inline'; - - const inp = document.createElement('input'); - inp.type = 'range'; - inp.min = String(item.min ?? 0); - inp.max = String(item.max ?? 100); - inp.step = String(item.step ?? 1); - inp.value = values[item.key] ?? item.default ?? item.min ?? 0; - inp.style.flex = '1'; - - const val = document.createElement('span'); - val.textContent = String(inp.value); - - inp.oninput = () => { - val.textContent = String(inp.value); - this.pluginManager.setPluginSetting(plugin.id, item.key, Number(inp.value)); - }; - - box.appendChild(inp); - box.appendChild(val); - row.appendChild(box); - } else if (item.type === 'choose') { - const sel = document.createElement('select'); - (item.options || []).forEach(opt => { - const op = document.createElement('option'); - op.value = String(opt); - op.textContent = String(opt); - sel.appendChild(op); - }); - sel.value = values[item.key] ?? item.default ?? ''; - sel.onchange = () => this.pluginManager.setPluginSetting(plugin.id, item.key, sel.value); - row.appendChild(sel); - } - - settingsWrap.appendChild(row); - }); - - parent.appendChild(settingsWrap); - } - _refreshPluginsList() { if (!this.ui.pluginsList) return; this.ui.pluginsList.innerHTML = ''; @@ -2623,25 +2093,25 @@ class GitHubPanelExtension { this.pluginManager.plugins.forEach((p, id) => { const row = document.createElement('div'); - row.className = 'gpp-plugin-card'; + Object.assign(row.style, Utils.itemStyle()); row.style.display = 'flex'; - row.style.flexDirection = 'column'; - row.style.gap = '8px'; - - const top = document.createElement('div'); - top.style.display = 'flex'; - top.style.justifyContent = 'space-between'; - top.style.alignItems = 'center'; - top.style.gap = '8px'; - top.style.flexWrap = 'wrap'; + row.style.justifyContent = 'space-between'; + row.style.alignItems = 'center'; const info = document.createElement('div'); - info.innerHTML = `${p.name}v${p.version}
ID: ${id}`; + let infoHtml = `${p.name}v${p.version}`; + if (p.description) { + infoHtml += `
${p.description}`; + } + if (p.tags && p.tags.length) { + infoHtml += `
🏷️ ${p.tags.join(', ')}`; + } + infoHtml += `
ID: ${id}`; + info.innerHTML = infoHtml; const btnBox = document.createElement('div'); btnBox.style.display = 'flex'; btnBox.style.gap = '5px'; - btnBox.style.flexWrap = 'wrap'; const copyBtn = UIComponents.createWindowButton('复制源码', { background: 'rgba(60,160,255,0.2)' @@ -2651,15 +2121,6 @@ class GitHubPanelExtension { alert('插件源码已复制!'); }; - const callHookBtn = UIComponents.createWindowButton('触发测试Hook'); - callHookBtn.onclick = () => { - this.pluginManager.trigger('plugin:test', { - pluginId: id, - time: Date.now() - }); - LoadingManager.setMessage(`已触发测试 Hook -> ${id}`); - }; - const delBtn = UIComponents.createWindowButton('卸载', { background: 'rgba(255,80,80,0.2)' }); @@ -2671,22 +2132,10 @@ class GitHubPanelExtension { }; btnBox.appendChild(copyBtn); - btnBox.appendChild(callHookBtn); btnBox.appendChild(delBtn); - top.appendChild(info); - top.appendChild(btnBox); - row.appendChild(top); - - const desc = document.createElement('div'); - desc.style.fontSize = '11px'; - desc.style.opacity = '0.72'; - const apiCount = (p._settingsSchema || []).length; - desc.textContent = `插件参数数量: ${apiCount}`; - row.appendChild(desc); - - this._renderPluginSettings(p, row); - + row.appendChild(info); + row.appendChild(btnBox); this.ui.pluginsList.appendChild(row); }); } @@ -2949,8 +2398,7 @@ class GitHubPanelExtension { display: 'flex', gap: '8px', marginBottom: '8px', - alignItems: 'center', - flexWrap: 'wrap' + alignItems: 'center' }); const branchSel = document.createElement('select'); Object.assign(branchSel.style, { @@ -3080,10 +2528,9 @@ class GitHubPanelExtension { marginBottom: '8px', alignItems: 'center' }); - const provSelWrap = Utils.select(['github', 'siliconflow', 'custom'], 'AI 提供商'); + const provSelWrap = Utils.select(['github', 'custom'], 'AI 提供商'); Utils.applySelectLabels(provSelWrap.sel, { github: 'GitHub (默认)', - siliconflow: '硅基流动', custom: '自定义' }); provSelWrap.sel.value = this.core.aiProvider; @@ -3116,8 +2563,7 @@ class GitHubPanelExtension { borderRadius: '6px', padding: '8px', boxSizing: 'border-box', - marginBottom: '6px', - resize: 'vertical' + marginBottom: '6px' }); const prompt = document.createElement('textarea'); @@ -3131,8 +2577,7 @@ class GitHubPanelExtension { borderRadius: '6px', padding: '8px', boxSizing: 'border-box', - marginBottom: '6px', - resize: 'vertical' + marginBottom: '6px' }); const cacheRow = document.createElement('div'); @@ -3215,7 +2660,6 @@ class GitHubPanelExtension { maxHeight: '220px', overflowY: 'auto' }); - outWrap.classList.add('gpp-scrollable'); const outPre = document.createElement('pre'); Object.assign(outPre.style, { whiteSpace: 'pre-wrap', @@ -3364,7 +2808,6 @@ class GitHubPanelExtension { this._loadMyRepos(); } else if (isAI) { LoadingManager.setMessage('AI Ready'); - this._refreshAIConfigUI(); this._renderAIOutput(); this._renderAICacheInfo(); } else if (isPlugins) { @@ -3425,59 +2868,6 @@ class GitHubPanelExtension { acts.appendChild(enterBtn); acts.appendChild(webBtn); - if (this.core.sfTranslateEnabled) { - const transBtn = UIComponents.createWindowButton('翻译', { - background: 'rgba(60,160,255,0.2)' - }); - transBtn.onclick = () => { - transBtn.textContent = '翻译中...'; - transBtn.disabled = true; - const transPlace = document.createElement('div'); - transPlace.style.color = '#8f8'; - transPlace.style.fontSize = '12px'; - transPlace.style.marginTop = '4px'; - desc.appendChild(transPlace); - this.core.aiManager.translate(item.description || 'No description', this.core.sfTranslateModel) - .then(result => { - transPlace.textContent = result; - transBtn.textContent = '已翻译'; - }) - .catch(err => { - transPlace.textContent = '翻译失败: ' + err.message; - transBtn.textContent = '翻译'; - transBtn.disabled = false; - }); - }; - acts.appendChild(transBtn); - } - - const introBtn = UIComponents.createWindowButton('硅基AI介绍', { - background: 'rgba(255,100,200,0.2)' - }); - introBtn.onclick = () => { - introBtn.textContent = '生成中...'; - introBtn.disabled = true; - const introPlace = document.createElement('div'); - introPlace.style.color = '#f8f'; - introPlace.style.fontSize = '12px'; - introPlace.style.marginTop = '4px'; - introPlace.style.whiteSpace = 'pre-wrap'; - desc.appendChild(introPlace); - - this.core.aiManager.generateIntro(item.full_name, item.description, 'internlm/internlm2_5-7b-chat') - .then(result => { - introPlace.textContent = result; - introBtn.textContent = '硅基AI介绍'; - introBtn.disabled = false; - }) - .catch(err => { - introPlace.textContent = '生成失败: ' + err.message; - introBtn.textContent = '硅基AI介绍'; - introBtn.disabled = false; - }); - }; - acts.appendChild(introBtn); - row.appendChild(top); row.appendChild(desc); row.appendChild(acts); @@ -3749,7 +3139,6 @@ class GitHubPanelExtension { mainContainer.style.display = 'flex'; mainContainer.style.flexDirection = 'column'; mainContainer.style.height = '100%'; - mainContainer.style.minHeight = '0'; if (lastCommitInfo) { const anno = document.createElement('div'); @@ -3795,11 +3184,6 @@ class GitHubPanelExtension { const toAI = UIComponents.createWindowButton('发送到AI'); toAI.disabled = true; - const translateBtn = UIComponents.createWindowButton('硅基翻译'); - translateBtn.style.background = this.core.sfTranslateEnabled ? 'rgba(60,160,255,0.3)' : 'rgba(255,255,255,0.05)'; - translateBtn.style.display = this.core.sfTranslateEnabled ? 'inline-block' : 'none'; - translateBtn.disabled = true; - const editBtn = UIComponents.createWindowButton('修改模式', { background: 'rgba(255, 165, 0, 0.3)' }); @@ -3826,7 +3210,6 @@ class GitHubPanelExtension { bar.appendChild(dl); bar.appendChild(del); bar.appendChild(toAI); - bar.appendChild(translateBtn); bar.appendChild(mdToggleBtn); bar.appendChild(heatBtn); bar.appendChild(editBtn); @@ -3840,8 +3223,7 @@ class GitHubPanelExtension { display: 'flex', gap: '10px', overflow: 'hidden', - position: 'relative', - minHeight: '0' + position: 'relative' }); const preWrap = document.createElement('div'); @@ -3850,7 +3232,6 @@ class GitHubPanelExtension { overflowY: 'auto', minWidth: '0' }); - preWrap.classList.add('gpp-scrollable'); const pre = document.createElement('pre'); Object.assign(pre.style, { whiteSpace: 'pre-wrap', @@ -3875,46 +3256,9 @@ class GitHubPanelExtension { resize: 'none', padding: '5px' }); - textarea.addEventListener('contextmenu', (e) => { - this.ui.editorTextarea = textarea; - this._handleEditorContextMenu(e) - }); - let touchTimer; - textarea.addEventListener('touchstart', (e) => { - touchTimer = setTimeout(() => { - this.ui.editorTextarea = textarea; - this._handleEditorContextMenu(e.touches[0]) - }, 800); - }); - textarea.addEventListener('touchend', () => clearTimeout(touchTimer)); contentBox.appendChild(textarea); this.ui.editorTextarea = textarea; - const transPanel = document.createElement('div'); - Object.assign(transPanel.style, { - width: '0', - display: 'none', - borderLeft: '1px solid #555', - paddingLeft: '10px', - flexDirection: 'column', - overflowY: 'auto', - transition: 'width 0.3s', - flexShrink: '0' - }); - transPanel.classList.add('gpp-scrollable'); - const transTitle = document.createElement('div'); - transTitle.innerHTML = `翻译结果`; - const transContent = document.createElement('pre'); - Object.assign(transContent.style, { - whiteSpace: 'pre-wrap', - wordBreak: 'break-word', - fontSize: '13px', - color: '#8f8' - }); - transPanel.appendChild(transTitle); - transPanel.appendChild(transContent); - contentBox.appendChild(transPanel); - mainContainer.appendChild(contentBox); this.ui.mainArea.appendChild(mainContainer); @@ -3923,9 +3267,7 @@ class GitHubPanelExtension { textarea, saveBtn, editBtn, - pre, - transPanel, - transContent + pre }; const ext = file.name.split('.').pop().toLowerCase(); @@ -3978,32 +3320,6 @@ class GitHubPanelExtension { this._renderAICacheInfo(); }; - if (this.core.sfTranslateEnabled) { - translateBtn.disabled = false; - translateBtn.onclick = async () => { - if (transPanel.style.display === 'none') { - transPanel.style.display = 'flex'; - transPanel.style.width = '45%'; - transContent.textContent = '正在翻译 (Translating)...'; - translateBtn.textContent = '翻译中...'; - translateBtn.disabled = true; - - try { - const result = await this.core.aiManager.translate(text, this.core.sfTranslateModel); - transContent.textContent = result; - } catch (err) { - transContent.textContent = '翻译失败: ' + err.message; - } finally { - translateBtn.textContent = '硅基翻译'; - translateBtn.disabled = false; - } - } else { - transPanel.style.display = 'none'; - transPanel.style.width = '0'; - } - }; - } - LoadingManager.setMessage(`Read ${file.size} bytes.`); this.pluginManager.trigger('file:open', { file, @@ -4152,7 +3468,6 @@ class GitHubPanelExtension { overflowY: 'auto', color: '#fff' }); - card.classList.add('gpp-scrollable'); card.innerHTML = `

文件贡献热力图: ${path.split('/').pop()}

`; @@ -4273,8 +3588,8 @@ class GitHubPanelExtension { this.ui.btnMinimize.textContent = '□'; this.ui._panelBody.style.display = 'none'; } else { - if (s.width === '100vw') s.width = '720px'; - if (s.height === '100vh' || parseInt(s.height) < 420) s.height = '740px'; + s.width = '720px'; + s.height = '740px'; s.borderRadius = '12px'; this.ui.btnMinimize.textContent = '_'; this.ui.btnFullscreen.textContent = '□'; @@ -4350,68 +3665,6 @@ class GitHubPanelExtension { r1.appendChild(modelSel.el); r1.appendChild(customInp); container.appendChild(r1); - } else if (this.core.aiProvider === 'siliconflow') { - const r1 = document.createElement('div'); - Object.assign(r1.style, rowStyle); - const keyInp = Utils.input('API Key (sk-...)'); - keyInp.value = this.core.siliconKey; - keyInp.type = 'password'; - keyInp.style.flex = '1'; - keyInp.onchange = () => { - this.core.siliconKey = keyInp.value; - this.core.updateAIConfig(); - }; - const sfModels = ['deepseek-ai/DeepSeek-R1-0528-Qwen3-8B', 'Qwen/Qwen2.5-Coder-7B-Instruct', 'THUDM/glm-4-9b-chat', 'internlm/internlm2_5-7b-chat', 'custom']; - const modelSel = Utils.select(sfModels, '模型'); - modelSel.sel.value = sfModels.includes(this.core.siliconModel) ? this.core.siliconModel : 'custom'; - const customInp = Utils.input('自定义模型 ID'); - customInp.style.flex = '1'; - customInp.style.display = modelSel.sel.value === 'custom' ? 'block' : 'none'; - if (modelSel.sel.value === 'custom') customInp.value = this.core.siliconModel; - modelSel.sel.onchange = () => { - if (modelSel.sel.value === 'custom') { - customInp.style.display = 'block'; - this.core.siliconModel = customInp.value; - } else { - customInp.style.display = 'none'; - this.core.siliconModel = modelSel.sel.value; - } - }; - customInp.oninput = () => { - this.core.siliconModel = customInp.value; - }; - r1.appendChild(keyInp); - r1.appendChild(modelSel.el); - r1.appendChild(customInp); - container.appendChild(r1); - - const r2 = document.createElement('div'); - Object.assign(r2.style, rowStyle); - const createSlider = (label, min, max, step, val, setter) => { - const d = document.createElement('div'); - d.style.fontSize = '12px'; - d.style.display = 'flex'; - d.style.alignItems = 'center'; - d.style.gap = '4px'; - const l = document.createElement('span'); - l.textContent = `${label}: ${val}`; - const s = document.createElement('input'); - s.type = 'range'; - s.min = min; - s.max = max; - s.step = step; - s.value = val; - s.oninput = () => { - l.textContent = `${label}: ${s.value}`; - setter(Number(s.value)); - }; - d.appendChild(l); - d.appendChild(s); - return d; - }; - r2.appendChild(createSlider('Temp', 0, 2, 0.1, this.core.siliconParams.temperature, v => this.core.siliconParams.temperature = v)); - r2.appendChild(createSlider('MaxTokens', 256, 8192, 256, this.core.siliconParams.maxTokens, v => this.core.siliconParams.maxTokens = v)); - container.appendChild(r2); } else if (this.core.aiProvider === 'custom') { const r1 = document.createElement('div'); Object.assign(r1.style, rowStyle); @@ -4420,7 +3673,7 @@ class GitHubPanelExtension { urlInp.style.flex = '2'; urlInp.onchange = () => { this.core.customAI.url = urlInp.value; - this.core.updateAIConfig(); + this.updateAIConfig(); }; const keyInp = Utils.input('API Key'); keyInp.value = this.core.customAI.key; @@ -4428,14 +3681,14 @@ class GitHubPanelExtension { keyInp.style.flex = '1'; keyInp.onchange = () => { this.core.customAI.key = keyInp.value; - this.core.updateAIConfig(); + this.updateAIConfig(); }; const modInp = Utils.input('Model Name'); modInp.value = this.core.customAI.model; modInp.style.flex = '1'; modInp.onchange = () => { this.core.customAI.model = modInp.value; - this.core.updateAIConfig(); + this.updateAIConfig(); }; r1.appendChild(urlInp); r1.appendChild(keyInp); @@ -4482,7 +3735,7 @@ class GitHubPanelExtension { await this.core.aiManager.stream([{ role: 'user', content: String(prompt) - }], () => { + }], (chunk) => { this._renderAIOutput(); }); @@ -4513,7 +3766,7 @@ class GitHubPanelExtension { }, { role: 'user', content: String(prompt) - }], () => { + }], (chunk) => { this._renderAIOutput(); }); @@ -4605,8 +3858,6 @@ class GitHubPanelExtension { this.core.cacheManager.repoCacheMeta.files += 1; this.core.cacheManager.repoCacheMeta.bytes += text.length; this.core.cacheManager.repoCacheMeta.t = Date.now(); - this.core.cacheManager.repoCacheMeta.owner = this.core.currentOwner; - this.core.cacheManager.repoCacheMeta.repo = this.core.currentRepo; this._renderAICacheInfo(); } catch (e) { @@ -4616,8 +3867,6 @@ class GitHubPanelExtension { } }; - this.core.cacheManager.repoCacheMeta.owner = this.core.currentOwner; - this.core.cacheManager.repoCacheMeta.repo = this.core.currentRepo; await walk(''); LoadingManager.setMessage('仓库递归读取完成' + (this.core.cacheManager.repoCacheMeta.truncated ? '(已截断)' : '')); } catch (e) { @@ -4666,10 +3915,8 @@ class GitHubPanelExtension { overflowY: 'auto', border: '1px solid #444', margin: '10px 0', - padding: '5px', - minHeight: '300px' + padding: '5px' }); - listWrap.classList.add('gpp-scrollable'); const files = Array.from(this.core.cacheManager.repoTextCache.entries()) .filter(e => e[1].type === 'file') @@ -4738,248 +3985,7 @@ class GitHubPanelExtension { document.body.appendChild(overlay); } - // ==================== AI重写面板 ==================== - - _handleEditorContextMenu(e) { - if (!this.core.isEditMode || !this.ui.editorTextarea) return; - - const start = this.ui.editorTextarea.selectionStart; - const end = this.ui.editorTextarea.selectionEnd; - if (start === end) return; - - e.preventDefault(); - this.core.lastSelection = { - start, - end - }; - - if (!this.ui.contextMenu) { - this.ui.contextMenu = document.createElement('div'); - Object.assign(this.ui.contextMenu.style, { - position: 'fixed', - background: '#333', - border: '1px solid #555', - borderRadius: '4px', - padding: '5px', - zIndex: 100001, - cursor: 'pointer', - boxShadow: '0 2px 10px rgba(0,0,0,0.5)' - }); - - const item = document.createElement('div'); - item.textContent = '✨ AI 区域改写'; - item.style.padding = '5px 10px'; - item.onmouseover = () => item.style.background = '#444'; - item.onmouseout = () => item.style.background = 'transparent'; - item.onclick = () => { - this.ui.contextMenu.style.display = 'none'; - this._showAIRewritePanel(); - }; - - this.ui.contextMenu.appendChild(item); - document.body.appendChild(this.ui.contextMenu); - } - - const x = e.clientX || e.pageX; - const y = e.clientY || e.pageY; - this.ui.contextMenu.style.left = x + 'px'; - this.ui.contextMenu.style.top = y + 'px'; - this.ui.contextMenu.style.display = 'block'; - - const hide = () => { - this.ui.contextMenu.style.display = 'none'; - document.removeEventListener('click', hide); - }; - setTimeout(() => document.addEventListener('click', hide), 100); - } - - _showAIRewritePanel() { - if (this.ui.aiRewritePanel) { - this.ui.aiRewritePanel.style.display = 'flex'; - return; - } - - const panel = document.createElement('div'); - Object.assign(panel.style, { - position: 'fixed', - right: '0', - top: '0', - width: '350px', - height: '100%', - background: '#222', - borderLeft: '1px solid #444', - zIndex: 100002, - display: 'flex', - flexDirection: 'column', - padding: '10px', - boxSizing: 'border-box', - boxShadow: '-5px 0 15px rgba(0,0,0,0.5)' - }); - - const header = document.createElement('div'); - header.innerHTML = 'AI 代码改写'; - header.style.marginBottom = '10px'; - header.style.display = 'flex'; - header.style.justifyContent = 'space-between'; - - const closeBtn = UIComponents.createWindowButton('×', { - padding: '2px 8px' - }); - closeBtn.onclick = () => { - panel.style.display = 'none'; - }; - header.appendChild(closeBtn); - panel.appendChild(header); - - const provSel = Utils.select(['github', 'siliconflow'], 'AI 提供商'); - provSel.sel.value = this.core.aiProvider === 'custom' ? 'github' : this.core.aiProvider; - panel.appendChild(provSel.el); - - const modelInp = Utils.input('模型名称 (e.g. gpt-4o)'); - modelInp.value = provSel.sel.value === 'github' ? 'gpt-4o' : 'deepseek-ai/DeepSeek-V3'; - modelInp.style.marginTop = '5px'; - panel.appendChild(modelInp); - - provSel.sel.onchange = () => { - modelInp.value = provSel.sel.value === 'github' ? 'gpt-4o' : 'deepseek-ai/DeepSeek-V3'; - }; - - const reqInp = document.createElement('textarea'); - reqInp.placeholder = '输入改写要求...'; - Object.assign(reqInp.style, { - width: '100%', - height: '60px', - marginTop: '10px', - background: '#333', - color: '#fff', - border: '1px solid #555', - resize: 'vertical' - }); - panel.appendChild(reqInp); - - const genBtn = UIComponents.createWindowButton('生成改写 (Generate)', { - marginTop: '10px', - width: '100%', - background: '#2a8' - }); - panel.appendChild(genBtn); - - const outWrap = document.createElement('div'); - Object.assign(outWrap.style, { - flex: '1', - marginTop: '10px', - background: '#111', - border: '1px solid #444', - padding: '5px', - overflowY: 'auto', - fontSize: '12px', - fontFamily: 'monospace', - whiteSpace: 'pre-wrap', - color: '#fff' - }); - outWrap.classList.add('gpp-scrollable'); - panel.appendChild(outWrap); - - const actionRow = document.createElement('div'); - Object.assign(actionRow.style, { - display: 'flex', - gap: '5px', - flexWrap: 'wrap', - marginTop: '10px' - }); - - const applyBtn = UIComponents.createWindowButton('应用替换 (Cache)', { - flex: '1', - background: '#d58' - }); - - const copyAllBtn = UIComponents.createWindowButton('复制完整代码', { - flex: '1' - }); - copyAllBtn.onclick = () => { - Utils.copyToClipboard(this.ui.editorTextarea.value); - }; - - const dlBtn = UIComponents.createWindowButton('下载仓库(Zip)', { - flex: '1' - }); - dlBtn.onclick = () => { - this._downloadRepoZip(); - }; - - const confirmBtn = UIComponents.createWindowButton('确认修改并上传', { - width: '100%', - marginTop: '5px', - background: '#28a745' - }); - confirmBtn.onclick = () => { - if (this.ui.fileViewRefs && this.ui.fileViewRefs.saveBtn) { - this.ui.fileViewRefs.saveBtn.click(); - } - }; - - actionRow.appendChild(applyBtn); - actionRow.appendChild(copyAllBtn); - actionRow.appendChild(dlBtn); - panel.appendChild(actionRow); - panel.appendChild(confirmBtn); - document.body.appendChild(panel); - this.ui.aiRewritePanel = panel; - - let generatedCode = ''; - genBtn.onclick = async () => { - outWrap.textContent = ''; - generatedCode = ''; - const selectedText = this.ui.editorTextarea.value.substring( - this.core.lastSelection.start, - this.core.lastSelection.end - ); - const prompt = `Rewrite the following code based on the requirement.\n\nCode:\n${selectedText}\n\nRequirement:\n${reqInp.value}\n\nOutput ONLY the rewritten code, no markdown block markers if possible.`; - - const oldProv = this.core.aiProvider; - const oldModel = this.core.aiProvider === 'github' ? this.core.githubModel : this.core.siliconModel; - - this.core.aiProvider = provSel.sel.value; - if (this.core.aiProvider === 'github') { - this.core.githubModel = modelInp.value; - } else { - this.core.siliconModel = modelInp.value; - } - this.core.updateAIConfig(); - - try { - await this.core.aiManager.stream([{ - role: 'user', - content: prompt - }], (chunk) => { - generatedCode += chunk; - outWrap.textContent = generatedCode; - outWrap.scrollTop = outWrap.scrollHeight; - }); - } catch (e) { - outWrap.textContent += `\nError: ${e.message}`; - } finally { - this.core.aiProvider = oldProv; - if (oldProv === 'github') { - this.core.githubModel = oldModel; - } else { - this.core.siliconModel = oldModel; - } - this.core.updateAIConfig(); - } - }; - - applyBtn.onclick = () => { - if (!generatedCode) return alert('请先生成代码'); - const fullText = this.ui.editorTextarea.value; - const before = fullText.substring(0, this.core.lastSelection.start); - const after = fullText.substring(this.core.lastSelection.end); - let cleanCode = generatedCode.replace(/^```\w*\n/, '').replace(/\n```$/, ''); - this.ui.editorTextarea.value = before + cleanCode + after; - this.core.lastSelection.end = this.core.lastSelection.start + cleanCode.length; - alert('已替换编辑器内容 (未上传)'); - }; - } + // ==================== 其他功能 ==================== _downloadRepoZip() { if (!this.core.currentOwner || !this.core.currentRepo) { @@ -4990,8 +3996,6 @@ class GitHubPanelExtension { window.open(url, '_blank'); } - // ==================== 其他功能 ==================== - async _showRepoHistory() { LoadingManager.setMessage('Loading commits...'); try { @@ -5246,8 +4250,7 @@ class GitHubPanelExtension { height: '200px', background: 'rgba(0,0,0,0.3)', color: '#fff', - border: '1px solid #555', - resize: 'vertical' + border: '1px solid #555' }); const btn = UIComponents.createWindowButton('Commit'); @@ -5666,7 +4669,8 @@ class GitHubPanelExtension { preWrap, textarea, saveBtn, - editBtn + editBtn, + pre } = this.ui.fileViewRefs; if (this.core.isEditMode) { @@ -5675,7 +4679,7 @@ class GitHubPanelExtension { saveBtn.style.display = 'inline-block'; editBtn.textContent = '取消修改'; editBtn.style.background = '#666'; - LoadingManager.setMessage('进入修改模式。右键选中区域可使用 AI 改写。'); + LoadingManager.setMessage('进入修改模式。'); } else { preWrap.style.display = 'block'; textarea.style.display = 'none'; @@ -5721,4 +4725,3 @@ class GitHubPanelExtension { // ==================== 注册扩展 ==================== Scratch.extensions.register(new GitHubPanelExtension()); -
${h}