VSCode插件开发:LongCat-Image-Edit的IDE集成方案
你是不是经常在编辑动物图片时,需要在浏览器、图片编辑器和代码编辑器之间来回切换?上传图片、输入指令、下载结果,一套流程下来,时间都花在工具切换上了。
今天咱们就来解决这个问题。我将手把手教你开发一个VSCode插件,让你直接在VSCode里就能调用LongCat-Image-Edit进行动物图片编辑。想象一下,在IDE里选中一张猫咪图片,输入“变成熊猫医生”,几秒钟后图片就自动编辑完成,整个过程完全不用离开VSCode。
这个插件开发完成后,你可以直接在VSCode的侧边栏里上传图片、输入自然语言指令,然后插件会调用LongCat-Image-Edit的API,把编辑好的图片直接显示在编辑器里。整个过程就像在IDE里使用一个内置的图片编辑工具一样自然。
1. 环境准备与项目初始化
1.1 安装必要的工具
首先确保你的电脑上已经安装了Node.js(建议版本16.x或更高)和VSCode。如果你还没有安装,可以去官网下载安装。
打开终端,检查一下Node.js是否安装成功:
node --version npm --version如果能看到版本号,说明安装成功了。
1.2 创建VSCode插件项目
VSCode官方提供了一个非常方便的工具来创建插件项目。在终端里运行:
npm install -g yo generator-code安装完成后,创建一个新的插件项目:
yo code这时候会有一个交互式的命令行界面,按照下面的选项选择:
? What type of extension do you want to create? New Extension (TypeScript) ? What's the name of your extension? longcat-image-edit-helper ? What's the identifier of your extension? longcat-image-edit-helper ? What's the description of your extension? Edit animal images with LongCat-Image-Edit directly in VSCode ? Initialize a git repository? Yes ? Which package manager to use? npm等待一会儿,项目就创建好了。用VSCode打开这个项目:
cd longcat-image-edit-helper code .1.3 项目结构初探
打开项目后,你会看到这样的目录结构:
longcat-image-edit-helper/ ├── src/ │ └── extension.ts # 插件的主入口文件 ├── package.json # 插件的配置文件 ├── tsconfig.json # TypeScript配置 └── .vscode/ # VSCode调试配置我们先来看看package.json,这是插件的核心配置文件。找到activationEvents和contributes部分,这些定义了插件什么时候激活,以及提供了哪些功能。
2. 插件界面设计与功能规划
2.1 设计插件界面
我们的插件需要一个用户界面来上传图片、输入指令、查看结果。VSCode提供了几种界面方案:
- 侧边栏视图:在活动栏添加一个图标,点击后显示一个完整的编辑面板
- Webview面板:创建一个独立的编辑窗口
- 状态栏按钮:快速操作的入口
我建议采用侧边栏视图+Webview的组合,这样既方便操作,又能提供丰富的交互。
2.2 修改package.json配置
打开package.json,在contributes部分添加我们的视图配置:
{ "contributes": { "viewsContainers": { "activitybar": [ { "id": "longcat-image-edit", "title": "LongCat Editor", "icon": "media/icon.svg" } ] }, "views": { "longcat-image-edit": [ { "id": "longcat.editor", "name": "Image Editor", "type": "webview" } ] }, "commands": [ { "command": "longcat.openEditor", "title": "Open LongCat Image Editor", "category": "LongCat" }, { "command": "longcat.editSelectedImage", "title": "Edit Selected Image with LongCat", "category": "LongCat" } ], "menus": { "editor/context": [ { "command": "longcat.editSelectedImage", "when": "resourceExtname == .jpg || resourceExtname == .png || resourceExtname == .jpeg", "group": "navigation" } ] } } }这段配置做了几件事:
- 在活动栏添加了一个图标
- 定义了一个Webview视图
- 注册了两个命令
- 在图片文件的右键菜单中添加了编辑选项
2.3 创建Webview界面
在src目录下创建一个新的文件夹webview,然后创建editor.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>LongCat Image Editor</title> <style> body { padding: 20px; font-family: var(--vscode-font-family); color: var(--vscode-foreground); background-color: var(--vscode-editor-background); } .container { max-width: 800px; margin: 0 auto; } .upload-area { border: 2px dashed var(--vscode-input-border); border-radius: 8px; padding: 40px; text-align: center; margin-bottom: 20px; cursor: pointer; transition: border-color 0.3s; } .upload-area:hover { border-color: var(--vscode-focusBorder); } .upload-area.dragover { border-color: var(--vscode-button-background); background-color: var(--vscode-button-secondaryBackground); } .preview-container { display: flex; gap: 20px; margin: 20px 0; } .preview-box { flex: 1; border: 1px solid var(--vscode-input-border); border-radius: 4px; padding: 10px; min-height: 200px; } .preview-box img { max-width: 100%; max-height: 300px; display: block; margin: 0 auto; } .instruction-input { width: 100%; height: 80px; padding: 10px; margin-bottom: 15px; border: 1px solid var(--vscode-input-border); border-radius: 4px; background-color: var(--vscode-input-background); color: var(--vscode-input-foreground); resize: vertical; } .examples { margin: 15px 0; font-size: 12px; color: var(--vscode-descriptionForeground); } .example-tag { display: inline-block; background: var(--vscode-badge-background); color: var(--vscode-badge-foreground); padding: 2px 8px; border-radius: 10px; margin: 2px; cursor: pointer; } .example-tag:hover { background: var(--vscode-button-hoverBackground); } .button-group { display: flex; gap: 10px; margin-top: 20px; } button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-family: var(--vscode-font-family); } .primary-btn { background-color: var(--vscode-button-background); color: var(--vscode-button-foreground); } .primary-btn:hover { background-color: var(--vscode-button-hoverBackground); } .secondary-btn { background-color: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); } .loading { display: none; text-align: center; margin: 20px 0; } .result-container { margin-top: 20px; border-top: 1px solid var(--vscode-panel-border); padding-top: 20px; } .history-item { border: 1px solid var(--vscode-input-border); border-radius: 4px; padding: 10px; margin-bottom: 10px; display: flex; align-items: center; gap: 10px; } .history-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 4px; } </style> </head> <body> <div class="container"> <h2>LongCat Image Editor</h2> <div class="upload-area" id="uploadArea"> <p> Drag & drop an image here, or click to select</p> <p style="font-size: 12px; color: var(--vscode-descriptionForeground);"> Supports JPG, PNG, JPEG (Max 5MB) </p> <input type="file" id="fileInput" accept=".jpg,.jpeg,.png" style="display: none;"> </div> <div class="preview-container"> <div class="preview-box"> <h4>Original Image</h4> <div id="originalPreview"></div> </div> <div class="preview-box"> <h4>Edited Result</h4> <div id="resultPreview"></div> </div> </div> <div> <label for="instruction">Edit Instruction:</label> <textarea id="instruction" class="instruction-input" placeholder="Describe how you want to edit the image. For example: '猫变熊猫医生', '给狗狗戴上墨镜', '把背景换成海滩'" ></textarea> <div class="examples"> <p>Try these examples:</p> <span class="example-tag">import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; export function activate(context: vscode.ExtensionContext) { console.log('LongCat Image Editor extension is now active!'); // 注册打开编辑器命令 let openEditorDisposable = vscode.commands.registerCommand('longcat.openEditor', () => { LongCatEditorPanel.createOrShow(context.extensionUri); }); // 注册编辑选中图片命令 let editSelectedImageDisposable = vscode.commands.registerCommand('longcat.editSelectedImage', (uri: vscode.Uri) => { if (uri) { LongCatEditorPanel.createOrShow(context.extensionUri, uri.fsPath); } }); context.subscriptions.push(openEditorDisposable, editSelectedImageDisposable); // 如果配置了自动打开,则在启动时打开编辑器 const config = vscode.workspace.getConfiguration('longcat'); if (config.get('autoOpenOnStart')) { vscode.commands.executeCommand('longcat.openEditor'); } } class LongCatEditorPanel { public static currentPanel: LongCatEditorPanel | undefined; private readonly _panel: vscode.WebviewPanel; private readonly _extensionUri: vscode.Uri; private _disposables: vscode.Disposable[] = []; private _selectedImagePath: string | undefined; public static createOrShow(extensionUri: vscode.Uri, imagePath?: string) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; // 如果已经有面板存在,则显示它 if (LongCatEditorPanel.currentPanel) { LongCatEditorPanel.currentPanel._panel.reveal(column); if (imagePath) { LongCatEditorPanel.currentPanel._selectedImagePath = imagePath; LongCatEditorPanel.currentPanel._updateWebview(); } return; } // 否则创建新的面板 const panel = vscode.window.createWebviewPanel( 'longcatEditor', 'LongCat Image Editor', column || vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ vscode.Uri.joinPath(extensionUri, 'media'), vscode.Uri.joinPath(extensionUri, 'out'), vscode.Uri.joinPath(extensionUri, 'webview') ] } ); LongCatEditorPanel.currentPanel = new LongCatEditorPanel(panel, extensionUri, imagePath); } private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, imagePath?: string) { this._panel = panel; this._extensionUri = extensionUri; this._selectedImagePath = imagePath; // 设置Webview的HTML内容 this._updateWebview(); // 监听面板关闭事件 this._panel.onDidDispose(() => this.dispose(), null, this._disposables); // 处理Webview消息 this._panel.webview.onDidReceiveMessage( async (message) => { switch (message.command) { case 'editImage': await this._handleEditImage(message.data); break; case 'saveImage': await this._handleSaveImage(message.data); break; case 'getImageData': await this._handleGetImageData(message.data); break; case 'showError': vscode.window.showErrorMessage(message.text); break; case 'showInfo': vscode.window.showInformationMessage(message.text); break; } }, null, this._disposables ); } private async _updateWebview() { const webview = this._panel.webview; this._panel.webview.html = this._getHtmlForWebview(webview); // 如果有选中的图片,发送给Webview if (this._selectedImagePath) { const imageData = await this._readImageAsBase64(this._selectedImagePath); webview.postMessage({ command: 'loadImage', data: { imageData: imageData, fileName: path.basename(this._selectedImagePath) } }); } } private _getHtmlForWebview(webview: vscode.Webview) { // 读取HTML文件 const htmlPath = path.join(this._extensionUri.fsPath, 'webview', 'editor.html'); let html = fs.readFileSync(htmlPath, 'utf8'); // 替换资源路径 const scriptUri = webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, 'webview', 'editor.js') ); // 在HTML中注入脚本 html = html.replace( '<!-- Script will be injected here -->', `<script src="${scriptUri}"></script>` ); return html; } private async _readImageAsBase64(imagePath: string): Promise<string> { try { const imageBuffer = await fs.promises.readFile(imagePath); const base64Image = imageBuffer.toString('base64'); const extension = path.extname(imagePath).toLowerCase(); let mimeType = 'image/jpeg'; if (extension === '.png') { mimeType = 'image/png'; } return `data:${mimeType};base64,${base64Image}`; } catch (error) { console.error('Error reading image:', error); throw error; } } private async _handleEditImage(data: any) { const { imageData, instruction } = data; // 显示进度通知 vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Editing image with LongCat...", cancellable: false }, async (progress) => { progress.report({ increment: 0 }); try { // 这里调用LongCat-Image-Edit的API // 注意:实际使用时需要替换为真实的API端点 const result = await this._callLongCatAPI(imageData, instruction); progress.report({ increment: 100 }); // 将结果发送回Webview this._panel.webview.postMessage({ command: 'editComplete', data: result }); vscode.window.showInformationMessage('Image edited successfully!'); } catch (error: any) { vscode.window.showErrorMessage(`Edit failed: ${error.message}`); this._panel.webview.postMessage({ command: 'editFailed', error: error.message }); } }); } private async _callLongCatAPI(imageData: string, instruction: string): Promise<any> { // 移除base64前缀 const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ''); // 这里应该是调用真实API的代码 // 由于LongCat-Image-Edit的具体API格式需要参考官方文档 // 这里提供一个示例结构 const apiUrl = 'https://api.example.com/longcat/edit'; // 替换为真实API const apiKey = vscode.workspace.getConfiguration('longcat').get('apiKey'); if (!apiKey) { throw new Error('API key not configured. Please set it in settings.'); } // 模拟API调用(实际使用时需要实现真实的HTTP请求) return new Promise((resolve) => { setTimeout(() => { // 这里应该返回真实的API响应 // 为了演示,我们返回一个模拟的base64图片 resolve({ success: true, editedImage: imageData, // 实际应该是编辑后的图片 message: 'Edit completed successfully' }); }, 3000); }); } private async _handleSaveImage(data: any) { const { imageData, fileName } = data; const options: vscode.SaveDialogOptions = { defaultUri: vscode.Uri.file(fileName || 'edited-image.png'), filters: { 'Images': ['png', 'jpg', 'jpeg'] } }; const fileUri = await vscode.window.showSaveDialog(options); if (fileUri) { try { // 移除base64前缀并保存文件 const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ''); const buffer = Buffer.from(base64Data, 'base64'); await fs.promises.writeFile(fileUri.fsPath, buffer); vscode.window.showInformationMessage(`Image saved to ${fileUri.fsPath}`); } catch (error: any) { vscode.window.showErrorMessage(`Save failed: ${error.message}`); } } } private async _handleGetImageData(data: any) { const { filePath } = data; try { const imageData = await this._readImageAsBase64(filePath); this._panel.webview.postMessage({ command: 'imageDataLoaded', data: imageData }); } catch (error: any) { this._panel.webview.postMessage({ command: 'loadImageFailed', error: error.message }); } } public dispose() { LongCatEditorPanel.currentPanel = undefined; this._panel.dispose(); while (this._disposables.length) { const disposable = this._disposables.pop(); if (disposable) { disposable.dispose(); } } } } export function deactivate() {}3.2 实现Webview的JavaScript逻辑
创建webview/editor.js:
(function() { const vscode = acquireVsCodeApi(); let currentImageData = null; let currentResultData = null; let editHistory = []; // DOM元素 const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const originalPreview = document.getElementById('originalPreview'); const resultPreview = document.getElementById('resultPreview'); const instructionInput = document.getElementById('instruction'); const editBtn = document.getElementById('editBtn'); const saveBtn = document.getElementById('saveBtn'); const clearBtn = document.getElementById('clearBtn'); const loading = document.getElementById('loading'); const historyList = document.getElementById('historyList'); const exampleTags = document.querySelectorAll('.example-tag'); // 初始化 function init() { setupEventListeners(); loadHistory(); // 监听来自扩展的消息 window.addEventListener('message', event => { const message = event.data; switch (message.command) { case 'loadImage': loadImageFromData(message.data); break; case 'editComplete': handleEditComplete(message.data); break; case 'editFailed': handleEditFailed(message.error); break; case 'imageDataLoaded': loadImageFromBase64(message.data); break; case 'loadImageFailed': showError('Failed to load image: ' + message.error); break; } }); } function setupEventListeners() { // 上传区域点击事件 uploadArea.addEventListener('click', () => { fileInput.click(); }); // 文件选择事件 fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { loadImageFromFile(file); } }); // 拖拽事件 uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) { loadImageFromFile(file); } else { showError('Please drop an image file (JPG, PNG, JPEG)'); } }); // 编辑按钮事件 editBtn.addEventListener('click', () => { if (!currentImageData) { showError('Please select an image first'); return; } const instruction = instructionInput.value.trim(); if (!instruction) { showError('Please enter an edit instruction'); return; } startEdit(currentImageData, instruction); }); // 保存按钮事件 saveBtn.addEventListener('click', () => { if (!currentResultData) { showError('No edited image to save'); return; } const fileName = generateFileName(); vscode.postMessage({ command: 'saveImage', data: { imageData: currentResultData, fileName: fileName } }); }); // 清除按钮事件 clearBtn.addEventListener('click', () => { clearAll(); }); // 示例标签点击事件 exampleTags.forEach(tag => { tag.addEventListener('click', () => { const instruction = tag.getAttribute('data-instruction'); instructionInput.value = instruction; instructionInput.focus(); }); }); // 指令输入框事件 instructionInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.ctrlKey) { editBtn.click(); } }); } function loadImageFromFile(file) { if (file.size > 5 * 1024 * 1024) { showError('Image size should be less than 5MB'); return; } const reader = new FileReader(); reader.onload = (e) => { currentImageData = e.target.result; updateOriginalPreview(currentImageData); instructionInput.focus(); }; reader.readAsDataURL(file); } function loadImageFromData(data) { currentImageData = data.imageData; updateOriginalPreview(currentImageData); instructionInput.focus(); showInfo(`Loaded image: ${data.fileName}`); } function loadImageFromBase64(base64Data) { currentImageData = base64Data; updateOriginalPreview(currentImageData); instructionInput.focus(); } function updateOriginalPreview(imageData) { originalPreview.innerHTML = `<img src="${imageData}" alt="Original Image">`; editBtn.disabled = false; } function updateResultPreview(imageData) { resultPreview.innerHTML = `<img src="${imageData}" alt="Edited Image">`; saveBtn.disabled = false; } function startEdit(imageData, instruction) { loading.style.display = 'block'; editBtn.disabled = true; vscode.postMessage({ command: 'editImage', data: { imageData: imageData, instruction: instruction } }); } function handleEditComplete(result) { loading.style.display = 'none'; editBtn.disabled = false; if (result.success && result.editedImage) { currentResultData = result.editedImage; updateResultPreview(currentResultData); // 添加到历史记录 addToHistory({ original: currentImageData, edited: currentResultData, instruction: instructionInput.value, timestamp: new Date().toISOString() }); showInfo(result.message || 'Edit completed successfully'); } else { showError(result.message || 'Edit failed'); } } function handleEditFailed(error) { loading.style.display = 'none'; editBtn.disabled = false; showError(error); } function addToHistory(item) { editHistory.unshift(item); if (editHistory.length > 10) { editHistory.pop(); } saveHistory(); updateHistoryDisplay(); } function updateHistoryDisplay() { historyList.innerHTML = ''; editHistory.forEach((item, index) => { const historyItem = document.createElement('div'); historyItem.className = 'history-item'; historyItem.innerHTML = ` <img src="${item.edited}" alt="History ${index + 1}"> <div> <div style="font-weight: bold;">${item.instruction}</div> <div style="font-size: 11px; color: var(--vscode-descriptionForeground);"> ${new Date(item.timestamp).toLocaleString()} </div> <div style="margin-top: 5px;"> <button onclick="restoreFromHistory(${index})" style="font-size: 11px; padding: 2px 8px;"> Restore </button> </div> </div> `; historyList.appendChild(historyItem); }); } function restoreFromHistory(index) { const item = editHistory[index]; if (item) { currentImageData = item.original; currentResultData = item.edited; instructionInput.value = item.instruction; updateOriginalPreview(currentImageData); updateResultPreview(currentResultData); showInfo('Restored from history'); } } function clearAll() { currentImageData = null; currentResultData = null; instructionInput.value = ''; originalPreview.innerHTML = '<p style="color: var(--vscode-descriptionForeground); text-align: center;">No image selected</p>'; resultPreview.innerHTML = '<p style="color: var(--vscode-descriptionForeground); text-align: center;">Edit result will appear here</p>'; editBtn.disabled = true; saveBtn.disabled = true; fileInput.value = ''; } function loadHistory() { const saved = localStorage.getItem('longcatEditHistory'); if (saved) { try { editHistory = JSON.parse(saved); updateHistoryDisplay(); } catch (e) { console.error('Failed to load history:', e); } } } function saveHistory() { localStorage.setItem('longcatEditHistory', JSON.stringify(editHistory)); } function generateFileName() { const now = new Date(); const dateStr = now.toISOString().slice(0, 19).replace(/[:T]/g, '-'); return `longcat-edited-${dateStr}.png`; } function showError(message) { vscode.postMessage({ command: 'showError', text: message }); } function showInfo(message) { vscode.postMessage({ command: 'showInfo', text: message }); } // 暴露函数给全局作用域 window.restoreFromHistory = restoreFromHistory; // 初始化 document.addEventListener('DOMContentLoaded', init); })();4. 插件配置与调试
4.1 添加插件配置
在package.json中添加配置选项:
{ "contributes": { "configuration": { "title": "LongCat Image Editor", "properties": { "longcat.apiKey": { "type": "string", "default": "", "description": "API key for LongCat-Image-Edit service" }, "longcat.apiEndpoint": { "type": "string", "default": "https://api.example.com/longcat/edit", "description": "API endpoint for LongCat-Image-Edit" }, "longcat.autoOpenOnStart": { "type": "boolean", "default": false, "description": "Automatically open editor when VSCode starts" }, "longcat.maxFileSize": { "type": "number", "default": 5242880, "description": "Maximum file size in bytes (default 5MB)" }, "longcat.defaultInstructions": { "type": "array", "default": [ "猫变熊猫医生", "给狗狗戴上墨镜", "把背景换成海滩", "变成卡通风格", "冬天变夏天" ], "description": "Default edit instruction examples" } } } } }4.2 调试插件
现在我们可以调试插件了。按F5键,VSCode会打开一个新的扩展开发主机窗口。
在新窗口中:
- 按
Ctrl+Shift+P打开命令面板 - 输入"Open LongCat Image Editor"并选择
- 插件面板就会在侧边栏打开
或者,你也可以在资源管理器中右键点击一个图片文件,选择"Edit Selected Image with LongCat"。
4.3 添加图标
在media目录下创建一个简单的图标文件icon.svg:
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <rect width="16" height="16" rx="3" fill="#4CAF50"/> <path d="M4 8L8 4L12 8L8 12Z" fill="white"/> <circle cx="8" cy="8" r="2" fill="#4CAF50"/> </svg>5. 插件打包与发布
5.1 安装打包工具
npm install -g @vscode/vsce5.2 创建发布配置
在package.json中添加发布相关字段:
{ "publisher": "your-publisher-name", "repository": { "type": "git", "url": "https://github.com/yourusername/longcat-image-edit-helper" }, "keywords": [ "image", "edit", "ai", "longcat", "animal", "photos" ], "categories": [ "Visualization", "Other" ], "engines": { "vscode": "^1.60.0" } }5.3 打包插件
vsce package这会生成一个.vsix文件,你可以直接安装这个文件到VSCode。
5.4 发布到市场
如果你想把插件发布到VSCode市场,需要:
- 创建一个发布者账号:https://aka.ms/vscode-create-publisher
- 获取个人访问令牌:https://aka.ms/vscode-marketplace-token
- 登录并发布:
vsce login your-publisher-name vsce publish6. 实际API集成
上面的代码中,我们使用了模拟的API调用。实际集成LongCat-Image-Edit时,你需要根据官方API文档修改_callLongCatAPI方法。
这里是一个示例实现:
private async _callLongCatAPI(imageData: string, instruction: string): Promise<any> { const config = vscode.workspace.getConfiguration('longcat'); const apiUrl = config.get('apiEndpoint', 'https://api.example.com/longcat/edit'); const apiKey = config.get('apiKey', ''); if (!apiKey) { throw new Error('LongCat API key not configured. Please set "longcat.apiKey" in settings.'); } // 移除base64前缀 const base64Data = imageData.replace(/^data:image\/\w+;base64,/, ''); const requestBody = { image: base64Data, instruction: instruction, parameters: { quality: "high", format: "png" } }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const result = await response.json(); if (!result.success) { throw new Error(result.message || 'API returned error'); } // 假设API返回base64格式的图片 return { success: true, editedImage: `data:image/png;base64,${result.edited_image}`, message: result.message }; } catch (error: any) { throw new Error(`API call failed: ${error.message}`); } }获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。