feat: 支持 MCP 图片内联渲染

This commit is contained in:
shiyue
2026-08-06 13:37:42 +08:00
parent 3f00cfe7a4
commit 3ddf21af85
6 changed files with 237 additions and 3 deletions

View File

@@ -5016,6 +5016,27 @@ function assertWindowsStartupContract() {
assert(source.includes('exit /b %APP_EXIT_CODE%'), 'Windows startup should return the server exit code to the caller');
}
function assertCcwebDisplayImageContract() {
const frontend = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
const styles = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
const server = fs.readFileSync(SERVER_PATH, 'utf8');
const { TOOLS, prepareImagePayload } = require(path.join(REPO_DIR, 'lib', 'ccweb-mcp-server'));
const imageHandler = server.slice(server.indexOf('function createCcwebDisplayImage'), server.indexOf('function findCcwebPromptMessage'));
assert(TOOLS.some((tool) => tool.name === 'ccweb_display_image'), 'ccweb MCP should expose the image display tool');
assert(server.includes("case 'ccweb_display_image':"), 'Internal MCP routing should handle image display calls');
assert(!imageHandler.includes("type: 'session_message'"), 'Image tool should not create a separate assistant message');
assert(frontend.includes('isCcwebDisplayImageTool'), 'Image tool results should render inside the current assistant bubble');
assert(frontend.includes('ccweb-display-image-tool'), 'Image tool should use an inline bubble component');
assert(frontend.includes("block.type === 'image'"), 'Assistant content renderer should support image blocks');
assert(frontend.includes('openAttachmentPreviewModal'), 'Assistant images should open the shared large preview');
assert(styles.includes('.assistant-image-button'), 'Assistant images should have stable bubble styling');
const remote = prepareImagePayload({ source: 'https://example.com/example.gif' });
assert(remote.ok && remote.image.url.endsWith('.gif'), 'Remote GIF URLs should be accepted');
const inline = prepareImagePayload({ source: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==' });
assert(inline.ok && inline.image.mimeType === 'image/gif', 'Base64 GIF images should be accepted');
assert(prepareImagePayload({ source: 'relative.png' }).code === 'invalid_local_path', 'Local images should require absolute paths');
}
async function main() {
const targetIndex = process.argv.indexOf('--target');
const regressionTarget = targetIndex >= 0 ? String(process.argv[targetIndex + 1] || '').trim() : String(process.env.CC_WEB_REGRESSION_TARGET || '').trim();
@@ -5111,6 +5132,11 @@ async function main() {
console.log('Windows startup regression checks passed.');
return;
}
if (regressionTarget === 'ccweb-display-image') {
assertCcwebDisplayImageContract();
console.log('ccweb display image regression checks passed.');
return;
}
if (regressionTarget === 'subagent-card-routing') {
assertCodexAppChildToolRoutingContract();
console.log('Sub-agent card routing regression checks passed.');
@@ -5147,6 +5173,7 @@ async function main() {
assertCodexAppChildToolRoutingContract();
assertMultiAgentV2CompatibilityContract();
assertWindowsStartupContract();
assertCcwebDisplayImageContract();
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-web-regression-'));
const configDir = path.join(tempRoot, 'config');