mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
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:
@@ -1753,6 +1753,7 @@ Below is the user's query. Please respond in the user's preferred language witho
|
||||
'docSemanticSearch',
|
||||
'webSearch',
|
||||
'docCompose',
|
||||
'codeArtifact',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -71,6 +71,7 @@ export const PromptConfigStrictSchema = z.object({
|
||||
'webSearch',
|
||||
// artifact tools
|
||||
'docCompose',
|
||||
'codeArtifact',
|
||||
])
|
||||
.array()
|
||||
.nullable()
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './code-artifact';
|
||||
export * from './doc-compose';
|
||||
export * from './doc-edit';
|
||||
export * from './doc-keyword-search';
|
||||
|
||||
Reference in New Issue
Block a user