From 6350a040a21917b4e4d8d1aba7f6bd012ab0fc3b Mon Sep 17 00:00:00 2001
From: Mickey <951203214@qq.com>
Date: Mon, 8 Jun 2026 11:20:07 +0800
Subject: [PATCH 1/8] feat: Added right-click functionality for the Folder
component (#1919)
* feat: folder
* feat: folder feat
* feat: folder feat
* feat: folder feat
* feat: folder feat
* chore: update version
* feat: lingxi-dev
* chore: update version
* chore: update version
* feat: lingxi-dev
* feat: lingxi-dev
* chore: update version
* chore: update version
* fix: exclude .dumi directory from TypeScript type checking
* revert: restore .dumi tsconfig to original state
* chore: update version
* chore: update version
* fix: add dumi type declarations to fix lint errors
* feat: lint
* feat: lint
* feat: lint
* feat: lint
---
.gitignore | 1 +
biome.json | 1 +
package.json | 2 +-
packages/x-card/package.json | 2 +-
packages/x-card/src/A2UI/utils.ts | 2 +-
packages/x-card/src/version.ts | 2 +-
packages/x-markdown/package.json | 2 +-
.../src/XMarkdown/__tests__/Renderer.test.ts | 156 +-
.../x-markdown/src/XMarkdown/core/Parser.ts | 2 +-
.../x-markdown/src/XMarkdown/core/Renderer.ts | 132 +-
packages/x-sdk/package.json | 2 +-
.../x-skill/.claude-plugin/marketplace.json | 2 +-
packages/x-skill/package.json | 2 +-
.../x-skill/skills-zh/use-x-chat/SKILL.md | 2 +-
packages/x-skill/skills-zh/x-card/SKILL.md | 2 +-
.../skills-zh/x-chat-provider/SKILL.md | 2 +-
.../x-skill/skills-zh/x-components/SKILL.md | 2 +-
.../x-skill/skills-zh/x-markdown/SKILL.md | 2 +-
packages/x-skill/skills-zh/x-request/SKILL.md | 2 +-
packages/x-skill/skills/use-x-chat/SKILL.md | 2 +-
packages/x-skill/skills/x-card/SKILL.md | 2 +-
.../x-skill/skills/x-chat-provider/SKILL.md | 2 +-
packages/x-skill/skills/x-components/SKILL.md | 2 +-
packages/x-skill/skills/x-markdown/SKILL.md | 2 +-
packages/x-skill/skills/x-request/SKILL.md | 2 +-
packages/x-skill/src/skill-meta.json | 12 +-
packages/x/.dumi/tsconfig.json | 10 +-
packages/x/components/attachments/util.ts | 2 +-
.../x/components/folder/DirectoryTree.tsx | 126 +-
.../folder/__tests__/index.test.tsx | 1256 ++++++++++++++++-
.../x/components/folder/demo/context-menu.md | 7 +
.../x/components/folder/demo/context-menu.tsx | 337 +++++
packages/x/components/folder/index.en-US.md | 38 +
packages/x/components/folder/index.tsx | 102 +-
packages/x/components/folder/index.zh-CN.md | 50 +-
packages/x/components/index.ts | 2 +-
.../docs/x-markdown/demo/streaming/format.tsx | 2 +-
.../custom-provider-width-ui.tsx | 2 +-
packages/x/package.json | 4 +-
packages/x/tsconfig.json | 4 +-
packages/x/typings/custom-typings.d.ts | 33 +
41 files changed, 2236 insertions(+), 83 deletions(-)
create mode 100644 packages/x/components/folder/demo/context-menu.md
create mode 100644 packages/x/components/folder/demo/context-menu.tsx
diff --git a/.gitignore b/.gitignore
index b9315e34f..6e0281c10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,3 +105,4 @@ packages/x-markdown/src/XMarkdown/__benchmark__/blob-report/
packages/x-markdown/src/XMarkdown/__benchmark__/playwright/.cache/
packages/x-markdown/src/XMarkdown/__benchmark__/playwright/.auth/
packages/x-markdown/src/XMarkdown/__benchmark__/test-results
+.codefuse/codefuse.json
\ No newline at end of file
diff --git a/biome.json b/biome.json
index df5ea27f6..711238204 100644
--- a/biome.json
+++ b/biome.json
@@ -3,6 +3,7 @@
"ignoreUnknown": true,
"includes": [
"**",
+ "!**/.dumi/**/*",
"!**/.dumi/tmp*",
"!**/dist/**/*",
"!**/es/**/*",
diff --git a/package.json b/package.json
index 6a66d267e..7b5da6907 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "x-mono",
- "version": "2.7.0",
+ "version": "2.9.0",
"private": true,
"scripts": {
"presite": "npm run prestart --workspaces",
diff --git a/packages/x-card/package.json b/packages/x-card/package.json
index 294a43bb8..1dce3143d 100644
--- a/packages/x-card/package.json
+++ b/packages/x-card/package.json
@@ -1,6 +1,6 @@
{
"name": "@ant-design/x-card",
- "version": "2.7.0",
+ "version": "2.9.0",
"description": "React card loader for dynamic content loading and management",
"keywords": [
"A2UI",
diff --git a/packages/x-card/src/A2UI/utils.ts b/packages/x-card/src/A2UI/utils.ts
index d091a5a84..4e4720a59 100644
--- a/packages/x-card/src/A2UI/utils.ts
+++ b/packages/x-card/src/A2UI/utils.ts
@@ -51,7 +51,7 @@ export function validateComponentAgainstCatalog(
const errors: string[] = [];
// If no catalog, pass by default
- if (!catalog || !catalog.components) {
+ if (!catalog?.components) {
return { valid: true, errors: [] };
}
diff --git a/packages/x-card/src/version.ts b/packages/x-card/src/version.ts
index b02cb2c4a..ac0da0f2f 100644
--- a/packages/x-card/src/version.ts
+++ b/packages/x-card/src/version.ts
@@ -1,2 +1,2 @@
// This file is auto generated by npm run version
-export default '2.7.0';
+export default '2.9.0';
diff --git a/packages/x-markdown/package.json b/packages/x-markdown/package.json
index fdb2512ba..0fa357f39 100644
--- a/packages/x-markdown/package.json
+++ b/packages/x-markdown/package.json
@@ -1,6 +1,6 @@
{
"name": "@ant-design/x-markdown",
- "version": "2.7.0",
+ "version": "2.9.0",
"scripts": {
"compile": "father build",
"tsc": "tsc --noEmit",
diff --git a/packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts b/packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts
index 4b64a28ef..39410888a 100644
--- a/packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts
+++ b/packages/x-markdown/src/XMarkdown/__tests__/Renderer.test.ts
@@ -1,4 +1,3 @@
-import DOMPurify from 'dompurify';
import React from 'react';
import Renderer from '../core/Renderer';
@@ -1032,20 +1031,28 @@ describe('Renderer', () => {
const renderer = new Renderer({ components });
- // Spy on DOMPurify.sanitize
- const sanitizeSpy = jest.spyOn(DOMPurify, 'sanitize');
+ const createElementSpy = jest.spyOn(React, 'createElement');
+ // Verify that custom tags are preserved and dangerous tags (script) are removed
const html = 'content';
renderer.processHtml(html);
- expect(sanitizeSpy).toHaveBeenCalledWith(
- html,
+ // The custom tag should be rendered (via React.createElement)
+ expect(createElementSpy).toHaveBeenCalledWith(
+ MockComponent,
expect.objectContaining({
- ADD_TAGS: expect.arrayContaining(['custom-tag']),
+ streamStatus: 'done',
}),
);
- sanitizeSpy.mockRestore();
+ // The script tag should be stripped by DOMPurify — no createElement
+ // call should have 'alert' or 'script' in its arguments
+ const scriptCalls = createElementSpy.mock.calls.filter(
+ (call) => typeof call[0] === 'string' && (call[0] === 'script' || call[0] === 'Script'),
+ );
+ expect(scriptCalls).toHaveLength(0);
+
+ createElementSpy.mockRestore();
});
it('should respect user dompurifyConfig', () => {
@@ -1063,22 +1070,30 @@ describe('Renderer', () => {
dompurifyConfig: userConfig,
});
- // Spy on DOMPurify.sanitize
- const sanitizeSpy = jest.spyOn(DOMPurify, 'sanitize');
+ const createElementSpy = jest.spyOn(React, 'createElement');
+ // With ALLOWED_TAGS and ALLOWED_ATTR, only the custom-tag with class
+ // attribute should be preserved; id attribute should be stripped
const html = 'content';
renderer.processHtml(html);
- expect(sanitizeSpy).toHaveBeenCalledWith(
- html,
+ // The custom tag should be rendered with the class attribute
+ expect(createElementSpy).toHaveBeenCalledWith(
+ MockComponent,
+ expect.objectContaining({
+ class: 'test',
+ }),
+ );
+
+ // Verify id attribute is stripped (not in ALLOWED_ATTR)
+ expect(createElementSpy).not.toHaveBeenCalledWith(
+ MockComponent,
expect.objectContaining({
- ALLOWED_TAGS: expect.arrayContaining(['custom-tag']),
- ALLOWED_ATTR: expect.arrayContaining(['class']),
- ADD_TAGS: expect.arrayContaining(['custom-tag']),
+ id: 'test-id',
}),
);
- sanitizeSpy.mockRestore();
+ createElementSpy.mockRestore();
});
});
@@ -1390,4 +1405,115 @@ describe('Renderer', () => {
createElementSpy.mockClear();
});
});
+
+ describe('createPatchedDOMPurify', () => {
+ it('should handle nodeName patch fallback for various node types', () => {
+ // Test that Renderer works correctly with patched DOMPurify
+ const components = {
+ 'test-component': MockComponent,
+ };
+
+ const renderer = new Renderer({ components });
+ const createElementSpy = jest.spyOn(React, 'createElement');
+
+ const html = 'content';
+ renderer.processHtml(html);
+
+ // Verify the component was rendered
+ expect(createElementSpy).toHaveBeenCalledWith(
+ MockComponent,
+ expect.objectContaining({
+ streamStatus: 'done',
+ }),
+ );
+
+ createElementSpy.mockRestore();
+ });
+
+ it('should handle DOMPurify with custom config', () => {
+ const components = {
+ 'custom-element': MockComponent,
+ };
+
+ const renderer = new Renderer({
+ components,
+ dompurifyConfig: {
+ ALLOWED_TAGS: ['custom-element'],
+ ALLOWED_ATTR: ['data-custom'],
+ },
+ });
+
+ const createElementSpy = jest.spyOn(React, 'createElement');
+
+ const html = 'content';
+ renderer.processHtml(html);
+
+ expect(createElementSpy).toHaveBeenCalledWith(
+ MockComponent,
+ expect.objectContaining({
+ 'data-custom': 'value',
+ streamStatus: 'done',
+ }),
+ );
+
+ createElementSpy.mockRestore();
+ });
+
+ it('should process HTML with text nodes', () => {
+ const components = {};
+ const renderer = new Renderer({ components });
+
+ // Simple HTML with just text
+ const html = '
Plain text content
';
+ const result = renderer.processHtml(html);
+
+ expect(result).toBeDefined();
+ });
+
+ it('should handle empty or invalid HTML gracefully', () => {
+ const components = {};
+ const renderer = new Renderer({ components });
+
+ // Empty string - should not throw
+ expect(() => renderer.render('')).not.toThrow();
+
+ // Whitespace only - should not throw
+ expect(() => renderer.render(' ')).not.toThrow();
+ });
+
+ it('should handle HTML with comments', () => {
+ const components = {};
+ const renderer = new Renderer({ components });
+
+ const html = 'content
';
+ const result = renderer.processHtml(html);
+
+ expect(result).toBeDefined();
+ });
+
+ it('should handle streaming mode with animation', () => {
+ const createElementSpy = jest.spyOn(React, 'createElement');
+
+ // Use a custom component to avoid AnimationText being skipped due to parent custom component check
+ const CustomComponent: React.FC = (props) => React.createElement('div', props);
+ const rendererWithComponent = new Renderer({
+ components: { 'custom-wrapper': CustomComponent },
+ streaming: {
+ enableAnimation: true,
+ animationConfig: {
+ fadeDuration: 100,
+ easing: 'ease-in',
+ },
+ },
+ });
+
+ const html = 'Animated text content';
+ rendererWithComponent.processHtml(html);
+
+ // Verify that createElement was called (meaning the renderer processed the HTML)
+ expect(createElementSpy).toHaveBeenCalled();
+
+ createElementSpy.mockRestore();
+ });
+ });
});
diff --git a/packages/x-markdown/src/XMarkdown/core/Parser.ts b/packages/x-markdown/src/XMarkdown/core/Parser.ts
index 17aeb32cc..3b0df4f2a 100644
--- a/packages/x-markdown/src/XMarkdown/core/Parser.ts
+++ b/packages/x-markdown/src/XMarkdown/core/Parser.ts
@@ -326,7 +326,7 @@ class Parser {
// If the last token is not text type, don't inject tail
// This prevents tail from appearing before incomplete components
- if (!lastNonEmptyToken || lastNonEmptyToken.type !== 'text') {
+ if (lastNonEmptyToken?.type !== 'text') {
return null;
}
diff --git a/packages/x-markdown/src/XMarkdown/core/Renderer.ts b/packages/x-markdown/src/XMarkdown/core/Renderer.ts
index 847563637..963d615e0 100644
--- a/packages/x-markdown/src/XMarkdown/core/Renderer.ts
+++ b/packages/x-markdown/src/XMarkdown/core/Renderer.ts
@@ -13,6 +13,132 @@ interface RendererOptions {
streaming?: XMarkdownProps['streaming'];
}
+/**
+ * Fix for DOMPurify 3.x in environments (e.g., happy-dom) where the cached
+ * Node.prototype getters return incorrect values for elements created in a
+ * different document context (the template content owner document).
+ *
+ * DOMPurify 3.x caches property getters from Node.prototype at module
+ * initialization time:
+ * const getNodeName = lookupGetter(Node.prototype, 'nodeName');
+ * const getNodeValue = lookupGetter(Node.prototype, 'nodeValue');
+ *
+ * In happy-dom, calling these cached getters on elements from the template
+ * content owner document returns empty string / null respectively, while
+ * direct property access returns the correct value. This causes DOMPurify
+ * to compute empty tagNames and strip nearly all elements from the output.
+ *
+ * The fix patches Node.prototype.nodeName and Node.prototype.nodeValue with
+ * safe fallbacks that use direct property access when the cached getter
+ * returns a falsy value. Then a fresh DOMPurify instance is created that
+ * picks up the patched getters.
+ *
+ * This is idempotent — calling it multiple times has no additional effect.
+ */
+let patchedDOMPurify: typeof DOMPurify | null = null;
+
+function createPatchedDOMPurify(): typeof DOMPurify {
+ if (patchedDOMPurify) return patchedDOMPurify;
+
+ if (typeof window !== 'undefined' && typeof Node !== 'undefined') {
+ // Detect if the Node.prototype.nodeName getter is broken for template
+ // content elements (the happy-dom bug).
+ try {
+ const template = document.createElement('template');
+ template.innerHTML = '';
+ const testEl = template.content.firstChild;
+ if (testEl) {
+ const nodeNameGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeName')?.get;
+ const nodeValueGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue')?.get;
+
+ // If the cached getter returns empty/falsy for an element that has
+ // a valid nodeName via direct access, we need to patch.
+ const needsNodeNamePatch =
+ nodeNameGetter && testEl.nodeName && !nodeNameGetter.call(testEl);
+
+ if (needsNodeNamePatch) {
+ const originalNodeNameGet = nodeNameGetter;
+ Object.defineProperty(Node.prototype, 'nodeName', {
+ get: function (this: Node) {
+ const value = originalNodeNameGet!.call(this);
+ if (value) return value;
+ // Fallback: for Element nodes, use tagName directly
+ if (this.nodeType === 1 && 'tagName' in this) {
+ return (this as unknown as { tagName: string }).tagName;
+ }
+ // Fallback: standard nodeName values for other node types
+ switch (this.nodeType) {
+ case 3:
+ return '#text';
+ case 4:
+ return '#cdata-section';
+ case 7:
+ return '#processing-instruction';
+ case 8:
+ return '#comment';
+ case 9:
+ return '#document';
+ case 11:
+ return '#document-fragment';
+ }
+ return value;
+ },
+ configurable: true,
+ enumerable: true,
+ });
+ }
+
+ // Check if nodeValue getter is also broken for text nodes
+ const needsNodeValuePatch =
+ nodeValueGetter &&
+ (() => {
+ template.innerHTML = 'test
';
+ const textNode = template.content.firstChild?.firstChild;
+ if (!textNode) return false;
+ const viaGetter = nodeValueGetter.call(textNode);
+ const viaDirect = textNode.nodeValue;
+ // getter returns null/undefined but direct access returns the value
+ return viaDirect != null && viaGetter == null;
+ })();
+
+ if (needsNodeValuePatch) {
+ const originalNodeValueGet = nodeValueGetter;
+ Object.defineProperty(Node.prototype, 'nodeValue', {
+ get: function (this: Node) {
+ const value = originalNodeValueGet!.call(this);
+ if (value !== null && value !== undefined) return value;
+ // For CharacterData nodes (text, comment, CDATA), fall back to
+ // the .data property which stores the text content in happy-dom.
+ if (
+ (this.nodeType === 3 || this.nodeType === 4 || this.nodeType === 8) &&
+ 'data' in this
+ ) {
+ return (this as CharacterData).data;
+ }
+ return value;
+ },
+ configurable: true,
+ enumerable: true,
+ });
+ }
+
+ if (needsNodeNamePatch || needsNodeValuePatch) {
+ // Create a fresh DOMPurify instance that will re-read (and cache)
+ // the patched getters from Node.prototype.
+ patchedDOMPurify = DOMPurify(window);
+ return patchedDOMPurify;
+ }
+ }
+ } catch {
+ // If detection fails (e.g., in SSR), fall through to default DOMPurify
+ }
+ }
+
+ // No patching needed — use the default exported instance
+ patchedDOMPurify = DOMPurify;
+ return patchedDOMPurify;
+}
+
class Renderer {
private readonly options: RendererOptions;
private static readonly NON_WHITESPACE_REGEX = /[^\r\n\s]+/;
@@ -124,9 +250,13 @@ class Renderer {
const unclosedTags = this.detectUnclosedTags(htmlString);
const cidRef = { current: 0, tagIndexes: {} };
+ // Get a DOMPurify instance that works correctly in environments where
+ // Node.prototype getters are broken for template content elements.
+ const purify = createPatchedDOMPurify();
+
// Use DOMPurify to clean HTML while preserving custom components and target attributes
const purifyConfig = this.configureDOMPurify();
- const cleanHtml = DOMPurify.sanitize(htmlString, purifyConfig);
+ const cleanHtml = purify.sanitize(htmlString, purifyConfig);
return parseHtml(cleanHtml, {
replace: this.createReplaceElement(unclosedTags, cidRef),
diff --git a/packages/x-sdk/package.json b/packages/x-sdk/package.json
index cd0f2b634..6abd83a64 100644
--- a/packages/x-sdk/package.json
+++ b/packages/x-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@ant-design/x-sdk",
- "version": "2.7.0",
+ "version": "2.9.0",
"homepage": "https://x.ant.design/x-sdks/introduce",
"bugs": {
"url": "https://github.com/ant-design/x/issues"
diff --git a/packages/x-skill/.claude-plugin/marketplace.json b/packages/x-skill/.claude-plugin/marketplace.json
index 3830d2324..5e5d02dfa 100644
--- a/packages/x-skill/.claude-plugin/marketplace.json
+++ b/packages/x-skill/.claude-plugin/marketplace.json
@@ -3,7 +3,7 @@
"metadata": {
"description": "Ant Design X intelligent agent skills collection",
"descriptionZh": "Ant Design X 智能技能库,提供完整的 AI 对话应用开发技能",
- "version": "2.7.0",
+ "version": "2.9.0",
"author": "Ant Design X Team",
"homepage": "https://github.com/ant-design/x",
"repository": {
diff --git a/packages/x-skill/package.json b/packages/x-skill/package.json
index 649874536..daf28db8b 100644
--- a/packages/x-skill/package.json
+++ b/packages/x-skill/package.json
@@ -1,6 +1,6 @@
{
"name": "@ant-design/x-skill",
- "version": "2.7.0",
+ "version": "2.9.0",
"description": "CLI tool for installing AI skills to development tools like Claude, Cursor, etc.",
"homepage": "https://x.ant.design/x-skills/introduce",
"bugs": {
diff --git a/packages/x-skill/skills-zh/use-x-chat/SKILL.md b/packages/x-skill/skills-zh/use-x-chat/SKILL.md
index a5c9d91d9..cfde6aa30 100644
--- a/packages/x-skill/skills-zh/use-x-chat/SKILL.md
+++ b/packages/x-skill/skills-zh/use-x-chat/SKILL.md
@@ -1,6 +1,6 @@
---
name: use-x-chat
-version: 2.7.0
+version: 2.9.0
description: 专注讲解如何使用 useXChat Hook,包括自定义 Provider 的集成、消息管理、错误处理、多会话管理等
---
diff --git a/packages/x-skill/skills-zh/x-card/SKILL.md b/packages/x-skill/skills-zh/x-card/SKILL.md
index 7621a95bb..d360a033c 100644
--- a/packages/x-skill/skills-zh/x-card/SKILL.md
+++ b/packages/x-skill/skills-zh/x-card/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-card
-version: 2.7.0
+version: 2.9.0
description: 当需要用 @ant-design/x-card 让 AI Agent 动态渲染富交互 UI 时使用——涵盖 XCard.Box、XCard.Card、A2UI v0.9 命令、数据绑定、Catalog、Actions 和流式渲染模式。
---
diff --git a/packages/x-skill/skills-zh/x-chat-provider/SKILL.md b/packages/x-skill/skills-zh/x-chat-provider/SKILL.md
index 4e670c577..a3c672e82 100644
--- a/packages/x-skill/skills-zh/x-chat-provider/SKILL.md
+++ b/packages/x-skill/skills-zh/x-chat-provider/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-chat-provider
-version: 2.7.0
+version: 2.9.0
description: 专注于自定义 Chat Provider 的实现,帮助将任意流式接口适配为 Ant Design X 标准格式
---
diff --git a/packages/x-skill/skills-zh/x-components/SKILL.md b/packages/x-skill/skills-zh/x-components/SKILL.md
index d016ecbfd..af58b4bb4 100644
--- a/packages/x-skill/skills-zh/x-components/SKILL.md
+++ b/packages/x-skill/skills-zh/x-components/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-components
-version: 2.7.0
+version: 2.9.0
description: 使用 @ant-design/x 组件库构建 AI 对话 UI 时使用 —— 涵盖 Bubble、Sender、Conversations、Prompts、ThoughtChain、Actions、Welcome、Attachments、Sources、Suggestion、Think、FileCard、CodeHighlighter、Mermaid、Folder、XProvider 和 Notification。
---
diff --git a/packages/x-skill/skills-zh/x-markdown/SKILL.md b/packages/x-skill/skills-zh/x-markdown/SKILL.md
index e148e4e55..b78b1e3a8 100644
--- a/packages/x-skill/skills-zh/x-markdown/SKILL.md
+++ b/packages/x-skill/skills-zh/x-markdown/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-markdown
-version: 2.7.0
+version: 2.9.0
description: 当任务涉及 @ant-design/x-markdown 的 Markdown 渲染、流式输出、自定义组件映射、插件、主题或聊天富内容展示时使用。
---
diff --git a/packages/x-skill/skills-zh/x-request/SKILL.md b/packages/x-skill/skills-zh/x-request/SKILL.md
index b99b65c9d..359c2a1bc 100644
--- a/packages/x-skill/skills-zh/x-request/SKILL.md
+++ b/packages/x-skill/skills-zh/x-request/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-request
-version: 2.7.0
+version: 2.9.0
description: 专注讲解 XRequest 的实际配置和使用,基于官方文档提供准确的配置说明
---
diff --git a/packages/x-skill/skills/use-x-chat/SKILL.md b/packages/x-skill/skills/use-x-chat/SKILL.md
index 6f43b33d4..f716670b0 100644
--- a/packages/x-skill/skills/use-x-chat/SKILL.md
+++ b/packages/x-skill/skills/use-x-chat/SKILL.md
@@ -1,6 +1,6 @@
---
name: use-x-chat
-version: 2.7.0
+version: 2.9.0
description: Focus on explaining how to use the useXChat Hook, including custom Provider integration, message management, error handling, multi-conversation management, and more
---
diff --git a/packages/x-skill/skills/x-card/SKILL.md b/packages/x-skill/skills/x-card/SKILL.md
index 62f0bb394..06eeb9bdf 100644
--- a/packages/x-skill/skills/x-card/SKILL.md
+++ b/packages/x-skill/skills/x-card/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-card
-version: 2.7.0
+version: 2.9.0
description: Use when building AI-driven UIs with @ant-design/x-card — covers XCard.Box, XCard.Card, A2UI v0.9 commands, data binding, catalogs, actions, and streaming patterns.
---
diff --git a/packages/x-skill/skills/x-chat-provider/SKILL.md b/packages/x-skill/skills/x-chat-provider/SKILL.md
index 3d36a118d..de5ee76d5 100644
--- a/packages/x-skill/skills/x-chat-provider/SKILL.md
+++ b/packages/x-skill/skills/x-chat-provider/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-chat-provider
-version: 2.7.0
+version: 2.9.0
description: Focus on implementing custom Chat Provider, helping to adapt any streaming interface to Ant Design X standard format
---
diff --git a/packages/x-skill/skills/x-components/SKILL.md b/packages/x-skill/skills/x-components/SKILL.md
index fb558100f..706fcb36e 100644
--- a/packages/x-skill/skills/x-components/SKILL.md
+++ b/packages/x-skill/skills/x-components/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-components
-version: 2.7.0
+version: 2.9.0
description: Use when building AI chat UIs with @ant-design/x components — covers Bubble, Sender, Conversations, Prompts, ThoughtChain, Actions, Welcome, Attachments, Sources, Suggestion, Think, FileCard, CodeHighlighter, Mermaid, Folder, XProvider, and Notification.
---
diff --git a/packages/x-skill/skills/x-markdown/SKILL.md b/packages/x-skill/skills/x-markdown/SKILL.md
index 981b412ea..7293ee282 100644
--- a/packages/x-skill/skills/x-markdown/SKILL.md
+++ b/packages/x-skill/skills/x-markdown/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-markdown
-version: 2.7.0
+version: 2.9.0
description: Use when building or reviewing Markdown rendering with @ant-design/x-markdown, including streaming Markdown, custom component mapping, plugins, themes, and chat-oriented rich content.
---
diff --git a/packages/x-skill/skills/x-request/SKILL.md b/packages/x-skill/skills/x-request/SKILL.md
index 69d728548..94f6d0f60 100644
--- a/packages/x-skill/skills/x-request/SKILL.md
+++ b/packages/x-skill/skills/x-request/SKILL.md
@@ -1,6 +1,6 @@
---
name: x-request
-version: 2.7.0
+version: 2.9.0
description: Focus on explaining the practical configuration and usage of XRequest, providing accurate configuration instructions based on official documentation
---
diff --git a/packages/x-skill/src/skill-meta.json b/packages/x-skill/src/skill-meta.json
index 524ceb32f..09c363439 100644
--- a/packages/x-skill/src/skill-meta.json
+++ b/packages/x-skill/src/skill-meta.json
@@ -6,7 +6,7 @@
{
"skill": "use-x-chat",
"name": "use-x-chat",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Focus on explaining how to use the useXChat Hook, including custom Provider integration, message management, error handling, multi-conversation management, and more",
"descZh": "专注讲解如何使用 useXChat Hook,包括自定义 Provider 的集成、消息管理、错误处理、多会话管理等",
"tags": ["chat", "hook", "sdk"]
@@ -14,7 +14,7 @@
{
"skill": "x-card",
"name": "x-card",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Use when building AI-driven UIs with @ant-design/x-card — covers XCard.Box, XCard.Card, A2UI v0.9 commands, data binding, catalogs, actions, and streaming patterns.",
"descZh": "当需要用 @ant-design/x-card 让 AI Agent 动态渲染富交互 UI 时使用——涵盖 XCard.Box、XCard.Card、A2UI v0.9 命令、数据绑定、Catalog、Actions 和流式渲染模式。",
"tags": ["sdk"]
@@ -22,7 +22,7 @@
{
"skill": "x-chat-provider",
"name": "x-chat-provider",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Focus on implementing custom Chat Provider, helping to adapt any streaming interface to Ant Design X standard format",
"descZh": "专注于自定义 Chat Provider 的实现,帮助将任意流式接口适配为 Ant Design X 标准格式",
"tags": ["chat", "provider", "sdk"]
@@ -30,7 +30,7 @@
{
"skill": "x-request",
"name": "x-request",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Focus on explaining the practical configuration and usage of XRequest, providing accurate configuration instructions based on official documentation",
"descZh": "专注讲解 XRequest 的实际配置和使用,基于官方文档提供准确的配置说明",
"tags": ["request", "sdk"]
@@ -44,7 +44,7 @@
{
"skill": "x-components",
"name": "x-components",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Use when building AI chat UIs with @ant-design/x components — covers Bubble, Sender, Conversations, Prompts, ThoughtChain, Actions, Welcome, Attachments, Sources, Suggestion, Think, FileCard, CodeHighlighter, Mermaid, Folder, XProvider, and Notification.",
"descZh": "使用 @ant-design/x 组件库构建 AI 对话 UI 时使用 —— 涵盖 Bubble、Sender、Conversations、Prompts、ThoughtChain、Actions、Welcome、Attachments、Sources、Suggestion、Think、FileCard、CodeHighlighter、Mermaid、Folder、XProvider 和 Notification。",
"tags": ["sdk"]
@@ -58,7 +58,7 @@
{
"skill": "x-markdown",
"name": "x-markdown",
- "version": "2.6.0",
+ "version": "2.9.0",
"desc": "Use when building or reviewing Markdown rendering with @ant-design/x-markdown, including streaming Markdown, custom component mapping, plugins, themes, and chat-oriented rich content.",
"descZh": "当任务涉及 @ant-design/x-markdown 的 Markdown 渲染、流式输出、自定义组件映射、插件、主题或聊天富内容展示时使用。",
"tags": ["sdk"]
diff --git a/packages/x/.dumi/tsconfig.json b/packages/x/.dumi/tsconfig.json
index 9b3a94dc3..f4657812a 100644
--- a/packages/x/.dumi/tsconfig.json
+++ b/packages/x/.dumi/tsconfig.json
@@ -1,7 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
- "resolveJsonModule": true
+ "skipLibCheck": true,
+ "noImplicitAny": false,
+ "strict": false,
+ "strictNullChecks": false
},
- "include": ["**/*", "../typings/index.d.ts"]
-}
+ "include": [],
+ "exclude": ["**/*"]
+}
\ No newline at end of file
diff --git a/packages/x/components/attachments/util.ts b/packages/x/components/attachments/util.ts
index 6f34280d5..6995fc56d 100644
--- a/packages/x/components/attachments/util.ts
+++ b/packages/x/components/attachments/util.ts
@@ -6,7 +6,7 @@ const MEASURE_SIZE = 200;
export function previewImage(file: File | Blob): Promise {
return new Promise((resolve) => {
- if (!file || !file.type || !isImageFileType(file.type)) {
+ if (!file?.type || !isImageFileType(file.type)) {
resolve('');
return;
}
diff --git a/packages/x/components/folder/DirectoryTree.tsx b/packages/x/components/folder/DirectoryTree.tsx
index af7e66f01..c96125200 100644
--- a/packages/x/components/folder/DirectoryTree.tsx
+++ b/packages/x/components/folder/DirectoryTree.tsx
@@ -1,17 +1,21 @@
import { FileOutlined, FolderOutlined } from '@ant-design/icons';
import type { TreeProps } from 'antd';
-import { Tree } from 'antd';
+import { Dropdown, Tree } from 'antd';
+import type { MenuProps } from 'antd/es/menu';
import type { DataNode } from 'antd/es/tree';
import clsx from 'clsx';
-import React, { useCallback } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
import { useXProviderContext } from '../x-provider';
import type { FolderProps } from '.';
+
// File tree node type
export interface FolderTreeData {
title: React.ReactNode;
path: string;
content?: string;
children?: FolderTreeData[];
+ /** Right-click context menu items, set to `false` to disable for this node. Function form receives full path key */
+ contextMenu?: MenuProps['items'] | false | ((key: string) => MenuProps['items']);
}
const { DirectoryTree: AntDirectoryTree } = Tree;
@@ -31,6 +35,10 @@ export interface DirectoryTreeProps {
style?: React.CSSProperties;
directoryTitle?: FolderProps['directoryTitle'];
prefixCls?: string;
+ /** Right-click context menu items, applies to all nodes. Can be overridden by `contextMenu` in `FolderTreeData` */
+ contextMenu?: MenuProps['items'] | ((node: FolderTreeData, key: string) => MenuProps['items']);
+ /** Callback when right-clicking a node */
+ onRightClick?: TreeProps['onRightClick'];
}
const DirectoryTree: React.FC = ({
@@ -48,7 +56,19 @@ const DirectoryTree: React.FC = ({
style,
directoryTitle,
prefixCls: customizePrefixCls,
+ contextMenu,
+ onRightClick,
}) => {
+ // ============================ Right-click Menu ============================
+ const [contextMenuOpen, setContextMenuOpen] = useState(false);
+ const [contextMenuItems, setContextMenuItems] = useState(undefined);
+
+ // Store all original node data indexed by key for quick lookup
+ const nodeDataMapRef = useRef