feat(core): support compose a doc tool (#13013)

#### PR Dependency Tree


* **PR #13013** 👈

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 document composition tool for AI chat, allowing users to
generate, preview, and save structured markdown documents directly from
chat interactions.
* Added an artifact preview panel for enhanced document previews within
the chat interface.
* Enabled dynamic content rendering in the chat panel's right section
for richer user experiences.

* **Improvements**
  * Sidebar maximum width increased for greater workspace flexibility.
* Enhanced chat message and split view styling for improved layout and
usability.

* **Bug Fixes**
  * None.

* **Other**
* Registered new custom elements for AI tools and artifact preview
functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Peng Xiao
2025-07-03 22:21:49 +08:00
committed by GitHub
parent 558279da29
commit cfc108613c
15 changed files with 530 additions and 4 deletions

View File

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

View File

@@ -19,6 +19,7 @@ import {
buildDocContentGetter,
buildDocKeywordSearchGetter,
buildDocSearchGetter,
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
createDocReadTool,
@@ -198,6 +199,10 @@ export abstract class CopilotProvider<C = any> {
tools.web_crawl_exa = createExaCrawlTool(this.AFFiNEConfig);
break;
}
case 'docCompose': {
tools.doc_compose = createDocComposeTool();
break;
}
}
}
return tools;

View File

@@ -69,6 +69,8 @@ export const PromptConfigStrictSchema = z.object({
'docSemanticSearch',
// work with exa/model internal tools
'webSearch',
// artifact tools
'docCompose',
])
.array()
.nullable()

View File

@@ -11,6 +11,7 @@ import {
import { ZodType } from 'zod';
import {
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
createDocReadTool,
@@ -388,6 +389,7 @@ export interface CustomAITools extends ToolSet {
doc_semantic_search: ReturnType<typeof createDocSemanticSearchTool>;
doc_keyword_search: ReturnType<typeof createDocKeywordSearchTool>;
doc_read: ReturnType<typeof createDocReadTool>;
doc_compose: ReturnType<typeof createDocComposeTool>;
web_search_exa: ReturnType<typeof createExaSearchTool>;
web_crawl_exa: ReturnType<typeof createExaCrawlTool>;
}
@@ -457,6 +459,10 @@ export class TextStreamParser {
result += `\nReading the doc "${chunk.args.doc_id}"\n`;
break;
}
case 'doc_compose': {
result += `\nWriting document "${chunk.args.title}"\n`;
break;
}
}
result = this.markAsCallout(result);
break;
@@ -486,6 +492,16 @@ export class TextStreamParser {
}
break;
}
case 'doc_compose': {
if (
chunk.result &&
typeof chunk.result === 'object' &&
'title' in chunk.result
) {
result += `\nDocument "${chunk.result.title}" created successfully with ${chunk.result.wordCount} words.\n`;
}
break;
}
case 'web_search_exa': {
if (Array.isArray(chunk.result)) {
result += `\n${this.getWebSearchLinks(chunk.result)}\n`;

View File

@@ -0,0 +1,70 @@
import { Logger } from '@nestjs/common';
import { tool } from 'ai';
import { z } from 'zod';
import { toolError } from './error';
const logger = new Logger('DocComposeTool');
export const createDocComposeTool = () => {
return tool({
description:
'Write a new document with markdown content. This tool creates structured markdown content for documents including titles, sections, and formatting.',
parameters: z.object({
title: z.string().describe('The title of the document'),
content: z
.string()
.describe(
'The main content to write in markdown format. Include proper markdown formatting like headers (# ## ###), lists (- * 1.), links [text](url), code blocks ```code```, and other markdown elements as needed.'
),
sections: z
.array(
z.object({
heading: z.string().describe('Section heading'),
content: z.string().describe('Section content in markdown'),
})
)
.optional()
.describe('Optional structured sections for the document'),
metadata: z
.object({
tags: z
.array(z.string())
.optional()
.describe('Optional tags for the document'),
description: z
.string()
.optional()
.describe('Optional brief description of the document'),
})
.optional()
.describe('Optional metadata for the document'),
}),
execute: async ({ title, content, sections, metadata }) => {
try {
let markdownContent = '';
markdownContent += `${content}\n\n`;
if (sections && sections.length > 0) {
for (const section of sections) {
markdownContent += `## ${section.heading}\n\n`;
markdownContent += `${section.content}\n\n`;
}
}
return {
title,
markdown: markdownContent.trim(),
wordCount: content.split(/\s+/).length,
metadata: metadata || {},
tags: metadata?.tags || [],
description: metadata?.description || '',
};
} catch (err: any) {
logger.error(`Failed to write document: ${title}`, err);
return toolError('Doc Write Failed', err.message);
}
},
});
};

View File

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