feat(core): code artifact tool (#13015)

<img width="1272" alt="image"
src="https://github.com/user-attachments/assets/429ec60a-48a9-490b-b45f-3ce7150ef32a"
/>


#### PR Dependency Tree


* **PR #13015** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a new AI tool for generating self-contained HTML artifacts,
including a dedicated interface for previewing, copying, and downloading
generated HTML.
* Added syntax highlighting and preview capabilities for HTML artifacts
in chat and tool panels.
* Integrated the new HTML artifact tool into the AI chat prompt and
Copilot toolset.

* **Enhancements**
* Improved artifact preview panel layout and sizing for a better user
experience.
* Enhanced HTML preview components to support both model-based and raw
HTML rendering.

* **Dependency Updates**
  * Added the "shiki" library for advanced syntax highlighting.

* **Bug Fixes**
  * None.

* **Chores**
* Updated internal imports and code structure to support new features
and maintain consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Peng Xiao
2025-07-04 07:39:51 +08:00
committed by GitHub
parent 53968f6f8c
commit 8ed7dea823
16 changed files with 671 additions and 56 deletions

View File

@@ -1753,6 +1753,7 @@ Below is the user's query. Please respond in the user's preferred language witho
'docSemanticSearch',
'webSearch',
'docCompose',
'codeArtifact',
],
},
};

View File

@@ -19,6 +19,7 @@ import {
buildDocContentGetter,
buildDocKeywordSearchGetter,
buildDocSearchGetter,
createCodeArtifactTool,
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
@@ -203,6 +204,10 @@ export abstract class CopilotProvider<C = any> {
tools.doc_compose = createDocComposeTool();
break;
}
case 'codeArtifact': {
tools.code_artifact = createCodeArtifactTool();
break;
}
}
}
return tools;

View File

@@ -71,6 +71,7 @@ export const PromptConfigStrictSchema = z.object({
'webSearch',
// artifact tools
'docCompose',
'codeArtifact',
])
.array()
.nullable()

View File

@@ -11,6 +11,7 @@ import {
import { ZodType } from 'zod';
import {
createCodeArtifactTool,
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
@@ -392,6 +393,7 @@ export interface CustomAITools extends ToolSet {
doc_compose: ReturnType<typeof createDocComposeTool>;
web_search_exa: ReturnType<typeof createExaSearchTool>;
web_crawl_exa: ReturnType<typeof createExaCrawlTool>;
code_artifact: ReturnType<typeof createCodeArtifactTool>;
}
type ChunkType = TextStreamPart<CustomAITools>['type'];

View File

@@ -0,0 +1,84 @@
import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import { toolError } from './error';
const logger = new Logger('CodeArtifactTool');
/**
* A copilot tool that produces a completely self-contained HTML artifact.
* The returned HTML must include <style> and <script> tags directly so that
* it can be saved as a single .html file and opened in any browser with no
* external dependencies.
*/
export const createCodeArtifactTool = () => {
return tool({
description:
'Generate a single-file HTML snippet (with inline <style> and <script>) that accomplishes the requested functionality. The final HTML should be runnable when saved as an .html file and opened in a browser. Do NOT reference external resources (CSS, JS, images) except through data URIs.',
parameters: z.object({
/**
* The <title> text that will appear in the browser tab.
*/
title: z.string().describe('The title of the HTML page'),
/**
* The raw HTML that should be placed inside <body>. *Do not* include
* <body> tags here the tool will wrap it for you.
*/
body: z
.string()
.describe('HTML markup that goes inside the <body> element'),
/**
* Optional CSS rules to be wrapped in a single <style> tag inside <head>.
*/
css: z
.string()
.optional()
.describe('CSS to inline in a <style> tag (omit if none).'),
/**
* Optional JavaScript code to be wrapped in a single <script> tag before
* </body>.
*/
js: z
.string()
.optional()
.describe('JavaScript to inline in a <script> tag (omit if none).'),
}),
execute: async ({ title, body, css = '', js = '' }) => {
try {
const parts: string[] = [];
parts.push('<!DOCTYPE html>');
parts.push('<html lang="en">');
parts.push('<head>');
parts.push('<meta charset="UTF-8" />');
parts.push(`<title>${title}</title>`);
if (css.trim().length) {
parts.push('<style>');
parts.push(css);
parts.push('</style>');
}
parts.push('</head>');
parts.push('<body>');
parts.push(body);
if (js.trim().length) {
parts.push('<script>');
parts.push(js);
parts.push('</script>');
}
parts.push('</body>');
parts.push('</html>');
const html = parts.join('\n');
return {
title,
html,
size: html.length,
};
} catch (err: any) {
logger.error(`Failed to compose code artifact (${title})`, err);
return toolError('Code Artifact Failed', err.message ?? String(err));
}
},
});
};

View File

@@ -1,3 +1,4 @@
export * from './code-artifact';
export * from './doc-compose';
export * from './doc-edit';
export * from './doc-keyword-search';