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()); - 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 + } + ] +} 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 d980523..0000000 Binary files "a/image/\345\257\273\350\267\257.jpg" and /dev/null differ diff --git "a/image/\345\257\273\350\267\257.svg" "b/image/\345\257\273\350\267\257.svg" new file mode 100644 index 0000000..f23644b --- /dev/null +++ "b/image/\345\257\273\350\267\257.svg" @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
${h}