Ver Fonte

first commit

yangg há 4 meses atrás
commit
1d34580892
100 ficheiros alterados com 6061 adições e 0 exclusões
  1. 13 0
      .editorconfig
  2. 15 0
      .eslintrc.js
  3. 11 0
      .gitignore
  4. 4 0
      .husky/commit-msg
  5. 4 0
      .husky/pre-commit
  6. 2 0
      .prettierignore
  7. 19 0
      .prettierrc.js
  8. 247 0
      CHANGELOG.md
  9. 21 0
      LICENSE
  10. 109 0
      README.en.md
  11. 111 0
      README.md
  12. 175 0
      docs/.vitepress/config.mts
  13. 7 0
      docs/.vitepress/config/plugins.ts
  14. 57 0
      docs/.vitepress/plugins/demo.ts
  15. 119 0
      docs/.vitepress/plugins/markdown-transform.ts
  16. 47 0
      docs/.vitepress/theme/components/PreferenceSwitch.vue
  17. 27 0
      docs/.vitepress/theme/index.ts
  18. 7 0
      docs/.vitepress/theme/style.css
  19. 31 0
      docs/.vitepress/vitepress/components/demo/vp-source-code.vue
  20. 75 0
      docs/.vitepress/vitepress/components/icons/hero.vue
  21. 185 0
      docs/.vitepress/vitepress/components/semantic/preview.vue
  22. 217 0
      docs/.vitepress/vitepress/components/vp-demo.vue
  23. 336 0
      docs/.vitepress/vitepress/components/vp-hero.vue
  24. 17 0
      docs/.vitepress/vitepress/components/vp-semantic.vue
  25. 22 0
      docs/.vitepress/vitepress/composables/source-code.ts
  26. 148 0
      docs/.vitepress/vitepress/composables/use-playground.ts
  27. 8 0
      docs/.vitepress/vitepress/index.ts
  28. 18 0
      docs/.vitepress/vitepress/utils/index.ts
  29. 106 0
      docs/component/attachments.md
  30. 158 0
      docs/component/bubble.md
  31. 89 0
      docs/component/conversations.md
  32. 4 0
      docs/component/overview.md
  33. 107 0
      docs/component/prompts.md
  34. 159 0
      docs/component/sender.md
  35. 78 0
      docs/component/suggestion.md
  36. 103 0
      docs/component/thought-chain.md
  37. 79 0
      docs/component/use-x-agent.md
  38. 69 0
      docs/component/use-x-chat.md
  39. 67 0
      docs/component/welcome.md
  40. 47 0
      docs/component/x-provider.md
  41. 64 0
      docs/component/x-request.md
  42. 59 0
      docs/component/x-stream.md
  43. 184 0
      docs/development/introduce.md
  44. 58 0
      docs/examples-setup/attachments/basic.vue
  45. 75 0
      docs/examples-setup/attachments/files.vue
  46. 72 0
      docs/examples-setup/attachments/overflow.vue
  47. 169 0
      docs/examples-setup/attachments/placeholder.vue
  48. 70 0
      docs/examples-setup/attachments/with-sender.vue
  49. 59 0
      docs/examples-setup/bubble/avatar-and-placement.vue
  50. 9 0
      docs/examples-setup/bubble/basic.vue
  51. 75 0
      docs/examples-setup/bubble/bubble-custom.vue
  52. 57 0
      docs/examples-setup/bubble/gpt-vis.vue
  53. 29 0
      docs/examples-setup/bubble/header-and-footer.vue
  54. 107 0
      docs/examples-setup/bubble/list-custom.vue
  55. 116 0
      docs/examples-setup/bubble/list.vue
  56. 24 0
      docs/examples-setup/bubble/loading.vue
  57. 45 0
      docs/examples-setup/bubble/markdown.vue
  58. 26 0
      docs/examples-setup/bubble/shape.vue
  59. 34 0
      docs/examples-setup/bubble/typing.vue
  60. 62 0
      docs/examples-setup/bubble/variant.vue
  61. 6 0
      docs/examples-setup/conversations/basic.vue
  62. 6 0
      docs/examples-setup/conversations/controlled-mode.vue
  63. 6 0
      docs/examples-setup/conversations/group-sort.vue
  64. 6 0
      docs/examples-setup/conversations/group.vue
  65. 6 0
      docs/examples-setup/conversations/with-menu.vue
  66. 6 0
      docs/examples-setup/playground/independent.vue
  67. 61 0
      docs/examples-setup/prompts/basic.vue
  68. 6 0
      docs/examples-setup/prompts/disabled.vue
  69. 6 0
      docs/examples-setup/prompts/flex-vertical.vue
  70. 6 0
      docs/examples-setup/prompts/flex-wrap-fixed.vue
  71. 6 0
      docs/examples-setup/prompts/flex-wrap.vue
  72. 6 0
      docs/examples-setup/prompts/nest.vue
  73. 6 0
      docs/examples-setup/sender/actions.vue
  74. 55 0
      docs/examples-setup/sender/basic.vue
  75. 6 0
      docs/examples-setup/sender/focus.vue
  76. 83 0
      docs/examples-setup/sender/header.vue
  77. 6 0
      docs/examples-setup/sender/headerFixed.vue
  78. 6 0
      docs/examples-setup/sender/pasteImage.vue
  79. 6 0
      docs/examples-setup/sender/sendStyle.vue
  80. 6 0
      docs/examples-setup/sender/speech.vue
  81. 6 0
      docs/examples-setup/sender/speechCustom.vue
  82. 6 0
      docs/examples-setup/sender/submitType.vue
  83. 61 0
      docs/examples-setup/suggestion/basic.vue
  84. 45 0
      docs/examples-setup/suggestion/block.vue
  85. 46 0
      docs/examples-setup/suggestion/trigger.vue
  86. 36 0
      docs/examples-setup/thought-chain/basic.vue
  87. 47 0
      docs/examples-setup/thought-chain/collapsible.vue
  88. 65 0
      docs/examples-setup/thought-chain/customization.vue
  89. 59 0
      docs/examples-setup/thought-chain/nested.vue
  90. 64 0
      docs/examples-setup/thought-chain/size.vue
  91. 101 0
      docs/examples-setup/thought-chain/status.vue
  92. 91 0
      docs/examples-setup/use-x-agent/custom.vue
  93. 111 0
      docs/examples-setup/use-x-agent/preset.vue
  94. 85 0
      docs/examples-setup/use-x-chat/basic.vue
  95. 123 0
      docs/examples-setup/use-x-chat/stream-cancel.vue
  96. 72 0
      docs/examples-setup/use-x-chat/stream.vue
  97. 148 0
      docs/examples-setup/use-x-chat/suggestions.vue
  98. 40 0
      docs/examples-setup/welcome/background.vue
  99. 12 0
      docs/examples-setup/welcome/basic.vue
  100. 30 0
      docs/examples-setup/welcome/variant.vue

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false

+ 15 - 0
.eslintrc.js

@@ -0,0 +1,15 @@
+module.exports = {
+  extends: ['plugin:vue/vue3-recommended'],
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    parser: '@typescript-eslint/parser',
+    sourceType: 'module',
+    ecmaVersion: 2020,
+    ecmaFeatures: {
+      jsx: true,
+    },
+  },
+  rules: {
+    'vue/one-component-per-file': 'off'
+  },
+};

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+node_modules
+/dist
+.dumi/tmp
+.dumi/tmp-test
+.dumi/tmp-production
+.DS_Store
+/coverage
+docs/.vitepress/cache
+docs/.vitepress/dist
+docs/.vitepress/.temp
+.vitepress/cache

+ 4 - 0
.husky/commit-msg

@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx commitlint --edit "${1}"

+ 4 - 0
.husky/pre-commit

@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged

+ 2 - 0
.prettierignore

@@ -0,0 +1,2 @@
+/dist
+*.yaml

+ 19 - 0
.prettierrc.js

@@ -0,0 +1,19 @@
+module.exports = {
+  pluginSearchDirs: false,
+  plugins: [
+    require.resolve('prettier-plugin-organize-imports'),
+    require.resolve('prettier-plugin-packagejson'),
+  ],
+  printWidth: 80,
+  proseWrap: 'never',
+  singleQuote: true,
+  trailingComma: 'all',
+  overrides: [
+    {
+      files: '*.md',
+      options: {
+        proseWrap: 'preserve',
+      },
+    },
+  ],
+};

+ 247 - 0
CHANGELOG.md

@@ -0,0 +1,247 @@
+# Changelog
+
+
+## v1.0.6
+[compare changes](https://undefined/undefined/compare/v1.0.5...v1.0.6)
+### 🚀 Enhancements
+- Add unplugin-vue-components resolver (#172)
+### 🩹 Fixes
+- **conversation:** Fixed display issue where long Conversation names would not be truncated (#166)
+- **bubble:** Fix avatar slot no work (#180)
+### 📖 Documentation
+- Update slogan (101db11)
+- Add auto import resolver doc (#179)
+- **sender:** Add basic setup demo (c2049ca)
+### ❤️ Contributors
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+- Bao ([@Bao0630](http://github.com/Bao0630))
+- YuePeng ([@erupts](http://github.com/erupts))
+
+## v1.0.5
+
+[compare changes](https://undefined/undefined/compare/v1.0.4...v1.0.5)
+
+### 🚀 Enhancements
+
+- Thought-chain TransitionCollapse (#152)
+- **play:** Add playground (#150)
+- **sender:** Add SpeechButton to actions (#168)
+
+### 🩹 Fixes
+
+- **sender:** Fix force-render no work (e8e69fc)
+- **bubble:** Fix useListData error when roles null (#165)
+
+### 📖 Documentation
+
+- Fix markdown-it type error (e62429f)
+- **attachments:** Add placeholder setup demo (38613ca)
+- Add bubble setup usage example「markdown」 (#154)
+- **useXChat:** Add use-x-chat setup example (#155)
+- **Attachments:** Add all example for template (#160)
+- **x-request:** Add x-request setup example (#158)
+- Add bubble setup usage example (done) (#127, #164)
+- Remove legacy-js-api warning (#118)
+- Add development introduce (#167)
+- Update img path (c4bcfec)
+
+### 📦 Build
+
+- Update umd global variable to antdx (e772226)
+
+### 🏡 Chore
+
+- Add jsdelivr badge (fe0be75)
+- Update badge (ca87e46)
+- Add unpkg conf (#146)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+- Wang Hao <710876099@qq.com>
+- LIKUN ([@StudyDayByDay](http://github.com/StudyDayByDay))
+- Bao ([@Bao0630](http://github.com/Bao0630))
+- Linhf123 ([@linhf123](http://github.com/linhf123))
+- 雨溪 ([@yuxi-ovo](http://github.com/yuxi-ovo))
+- EralChen ([@EralChen](http://github.com/EralChen))
+
+## v1.0.4
+
+[compare changes](https://undefined/undefined/compare/v1.0.3...v1.0.4)
+
+### 🚀 Enhancements
+
+- Add preference-switch for docs (#124)
+- Semantic dom (#140)
+
+### 🩹 Fixes
+
+- **docs:** Fix md lang for vue setup demo source (#130)
+- **attachment:** Fix placeholder slot no work (#144)
+
+### 📖 Documentation
+
+- Add welcome and x-stream setup usage example (#127, #128)
+- Add prompts/basic.vue setup usage example (#127, #128, #131)
+- Add thought-chain setup usage example (#132)
+- Add bubble  setup usage example (#127, #135)
+- Add use-x-agent setup usage example (#137)
+- **attachments:** Add basic demo for setup examples (#139)
+- **suggestion:** Add suggestion setup example (#142)
+- **attachments:** Add with sender demo and update basic demo (#100)
+- **attachments:** Add with-sender setup demo file (a03e6d1)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+- Linhf123 ([@linhf123](http://github.com/linhf123))
+- Bao ([@Bao0630](http://github.com/Bao0630))
+- Zoey Zhao ([@zoeyzhao19](http://github.com/zoeyzhao19))
+- 归谜 ([@vhxubo](http://github.com/vhxubo))
+- LIKUN ([@StudyDayByDay](http://github.com/StudyDayByDay))
+- Zhangqiang ([@JACK-ZHANG-coming](http://github.com/JACK-ZHANG-coming))
+
+## v1.0.3
+
+[compare changes](https://undefined/undefined/compare/v1.0.2...v1.0.3)
+
+### 🚀 Enhancements
+
+- Add netlify redirects (#117)
+
+### 🩹 Fixes
+
+- **attachments:** Fix ref err (#122)
+
+### 📖 Documentation
+
+- Fix hy load err by remove App comp (#121)
+- Add and-design-vue link (#107)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+
+## v1.0.2
+
+[compare changes](https://undefined/undefined/compare/v1.0.1...v1.0.2)
+
+### 🚀 Enhancements
+
+- **bubble:** Add index to roles function for bubble list (#108)
+- Add stackblitz playground (#115)
+
+### 🩹 Fixes
+
+- **attachments:** Fix Attachments broken image (#94)
+- Fix the problem that the BubbleList component cannot trigger scrolling (#101)
+
+### 📦 Build
+
+- Fix pkg error (b0aa3da)
+
+### 🏡 Chore
+
+- Enhance readme (#106)
+- Remove react deps (#82)
+
+### ❤️ Contributors
+
+- Zoey Zhao ([@zoeyzhao19](http://github.com/zoeyzhao19))
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+- Wangyonghui0394 ([@wangyonghui0394](http://github.com/wangyonghui0394))
+- Linhf123 ([@linhf123](http://github.com/linhf123))
+- 李慧峰 ([@enootttt](http://github.com/enootttt))
+
+## v1.0.1
+
+[compare changes](https://undefined/undefined/compare/v1.0.0...v1.0.1)
+
+### 🚀 Enhancements
+
+- **theme:** More detailed documentation is displayed (#87)
+
+### 🩹 Fixes
+
+- **conversations:** Fix item label render err when it is a vnode (#92)
+
+### 📖 Documentation
+
+- Add ant design x spec link (#85)
+- Add ant design x link in nav (#86)
+
+### 🏡 Chore
+
+- Enhance readme for English (#89)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+- Wangyonghui0394 ([@wangyonghui0394](http://github.com/wangyonghui0394))
+- 雨溪 ([@yuxi-ovo](http://github.com/yuxi-ovo))
+
+## v1.0.0
+
+[compare changes](https://undefined/undefined/compare/v0.1.4...v1.0.0)
+
+### 🚀 Enhancements
+
+- **bubble:** Add slots (#46)
+- **welcome:** Add slots (#47)
+- **prompts:** Add title slot (#57)
+- **suggestion:** Add default slot (#60)
+- **use-x-agent:** Use array exports instead of object exports (#49)
+- **attachments:** Add placeholder slot (#58)
+
+### 🏡 Chore
+
+- Remove dumi (#79)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+
+## v0.1.4
+
+[compare changes](https://undefined/undefined/compare/v0.1.3...v0.1.4)
+
+### 🩹 Fixes
+
+- Fix prop type for defineProps (#72)
+- **sender:** Fix actions style (#78)
+
+### 📖 Documentation
+
+- **attachments:** Fix typo and add file-card props description (#59)
+
+### 🏡 Chore
+
+- Add more badge (#54)
+
+### 🤖 CI
+
+- Fix ci (#75)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+
+## v0.1.3
+
+[compare changes](https://undefined/undefined/compare/0.1.2...v0.1.3)
+
+### 🩹 Fixes
+
+- **attachments:** Fix beforeUpload prop no work (#65)
+
+### 🏡 Chore
+
+- Configure Renovate (#50)
+- Update reademe (8a74002)
+- Add ci and update lint rules (#55)
+- Add changelogen and release script (#56)
+
+### ❤️ Contributors
+
+- Wzc520pyfm ([@wzc520pyfm](http://github.com/wzc520pyfm))
+

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) wzc520pyfm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 109 - 0
README.en.md

@@ -0,0 +1,109 @@
+# Ant Design X Vue
+
+[![npm version][npm-version-src]][npm-version-href]
+[![npm downloads][npm-downloads-src]][npm-downloads-href]
+[![bundle][bundle-src]][bundle-href]
+[![Github Actions][github-actions-src]][github-actions-href]
+![Netlify Build][netlify-build-src]
+[![License][license-src]][license-href]
+
+Ant Design X For Vue. 🔥 [中文](./README.md)
+
+This is the implementation of [Ant Design X](https://github.com/ant-design/x) for Vue.
+
+## ✨ Features
+
+- 🌈 Built on the RICH interaction paradigm, delivering an exceptional AI interaction experience.
+- 🧩 Covers most AI dialogue scenarios, empowering you to quickly build personalized AI interaction interfaces.
+- ⚡ Easily connect with inference services compatible with OpenAI standards.
+- 🔄 Provides powerful tools for managing data flows, enhancing development efficiency.
+- 📦 Offers multiple templates for quickly starting LUI application development.
+- 🛡 Developed with TypeScript, ensuring robust type coverage to improve the development experience and reliability.
+- 🎨 Supports fine-grained style adjustments to meet diverse use cases and personalization needs.
+
+
+## Usage
+
+First, install packages `ant-design-vue` and `ant-design-x-vue` to your project, you can run following commands in your project directory:
+
+> pnpm add ant-design-vue ant-design-x-vue
+
+Second, import components from `ant-design-vue` library:
+
+```ts
+<script setup lang="ts">
+import { Bubble, XProvider } from 'ant-design-x-vue';
+</script>
+
+<template>
+  <Bubble content="Hello AI" />
+</template>
+```
+
+## Development
+
+```bash
+# install dependencies
+$ pnpm install
+
+# develop library by docs demo
+$ pnpm docs:dev
+
+# build library source code
+$ pnpm run build
+
+# build docs code
+$ pnpm run docs:build
+
+# Locally preview the production build.
+$ pnpm run docs:preview
+
+# Lint
+$ pnpm lint
+```
+
+## Links
+
+* [Vue](https://vuejs.org/)
+
+* [Ant Design Vue](https://www.antdv.com/)
+
+* [Ant Design X Of React](https://x.ant.design/)
+
+## Contributing
+
+Would you like to change the world with us?
+
+Please reference the [Roadmap](https://github.com/wzc520pyfm/ant-design-x-vue/issues/1) and pick up any one you like.
+
+## Contributors
+
+<a href="https://github.com/wzc520pyfm/ant-design-x-vue/graphs/contributors">
+  <img src="https://contrib.rocks/image?repo=wzc520pyfm/ant-design-x-vue" />
+</a>
+
+
+## LICENSE
+
+MIT
+
+<!-- Badges -->
+
+[npm-version-src]: https://img.shields.io/npm/v/ant-design-x-vue.svg?style=flat
+[npm-version-href]: https://npmjs.com/package/ant-design-x-vue
+[npm-downloads-src]: https://img.shields.io/npm/dm/ant-design-x-vue.svg?style=flat
+[npm-downloads-href]: https://npmjs.com/package/ant-design-x-vue
+[bundle-src]: https://img.shields.io/bundlephobia/minzip/ant-design-x-vue?style=flat
+[bundle-href]: https://bundlephobia.com/result?p=ant-design-x-vue
+[github-actions-src]: https://img.shields.io/github/actions/workflow/status/wzc520pyfm/ant-design-x-vue/ci.yml?branch=main&style=flat
+[github-actions-href]: https://github.com/wzc520pyfm/ant-design-x-vue/actions?query=workflow%3Aci
+[netlify-build-src]: https://img.shields.io/netlify/cb006e4e-afce-4c3e-9652-6f8a065b5b6e
+[license-src]: https://img.shields.io/github/license/wzc520pyfm/ant-design-x-vue?style=flat
+[license-href]: https://github.com/wzc520pyfm/ant-design-x-vue/blob/main/LICENSE
+
+## Connect Us
+### Wechat Public Account
+![Ant-Design-X Vue](./docs/public/images//wechat_public_account.jpg)
+
+### QQ Group
+![QQ 群](./docs/public/images/QQ_group_1.jpg)

+ 111 - 0
README.md

@@ -0,0 +1,111 @@
+# Ant Design X Vue
+
+[![npm version][npm-version-src]][npm-version-href]
+[![npm downloads][npm-downloads-src]][npm-downloads-href]
+[![bundle][bundle-src]][bundle-href]
+[![Github Actions][github-actions-src]][github-actions-href]
+![Netlify Build][netlify-build-src]
+[![License][license-src]][license-href]
+
+[![](https://data.jsdelivr.com/v1/package/npm/ant-design-x-vue/badge)](https://www.jsdelivr.com/package/npm/ant-design-x-vue)
+
+Ant Design X For Vue. 🔥 [English](./README.en.md)
+
+这里是[Ant Design X](https://github.com/ant-design/x)的Vue实现。
+
+## ✨ 特性
+
+- 🌈 基于 RICH 交互范式,提供卓越的 AI 交互体验
+- 🧩 覆盖绝大部分 AI 对话场景,助力快速构建个性化 AI 交互页面
+- ⚡ 轻松对接符合 OpenAI 标准的模型推理服务
+- 🔄 提供好用的数据流管理功能,让开发更高效
+- 📦 提供多种模板,快速启动 LUI 应用开发
+- 🛡 采用 TypeScript 开发,提供完整类型支持,提升开发体验与可靠性
+- 🎨 支持细粒度的样式调整,满足各种场景的个性化需求
+
+
+## Usage
+
+安装两个依赖 `ant-design-vue` and `ant-design-x-vue` 到你的项目中, 你可以参照如下命令行:
+
+> pnpm add ant-design-vue ant-design-x-vue
+
+然后在你的项目里导入 `ant-design-vue`:
+
+```ts
+<script setup lang="ts">
+import { Bubble, XProvider } from 'ant-design-x-vue';
+</script>
+
+<template>
+  <Bubble content="Hello AI" />
+</template>
+```
+
+## 开发
+
+```bash
+# install dependencies
+$ pnpm install
+
+# develop library by docs demo
+$ pnpm docs:dev
+
+# build library source code
+$ pnpm run build
+
+# build docs code
+$ pnpm run docs:build
+
+# Locally preview the production build.
+$ pnpm run docs:preview
+
+# Lint
+$ pnpm lint
+```
+
+## 链接
+
+* [Vue](https://vuejs.org/)
+
+* [Ant Design Vue](https://www.antdv.com/)
+
+* [Ant Design X Of React](https://x.ant.design/)
+
+## Contributing
+
+您的贡献至关重要!
+
+您可以参考[Roadmap](https://github.com/wzc520pyfm/ant-design-x-vue/issues/1),从中挑选您感兴趣的部分。
+
+## 贡献者
+
+<a href="https://github.com/wzc520pyfm/ant-design-x-vue/graphs/contributors">
+  <img src="https://contrib.rocks/image?repo=wzc520pyfm/ant-design-x-vue" />
+</a>
+
+## 交流
+
+### 微信公众号
+![Ant-Design-X Vue](./docs/public/images//wechat_public_account.jpg)
+
+### QQ 群
+![QQ 群](./docs/public/images/QQ_group_1.jpg)
+
+## LICENSE
+
+MIT
+
+<!-- Badges -->
+
+[npm-version-src]: https://img.shields.io/npm/v/ant-design-x-vue.svg?style=flat
+[npm-version-href]: https://npmjs.com/package/ant-design-x-vue
+[npm-downloads-src]: https://img.shields.io/npm/dt/ant-design-x-vue.svg?style=flat
+[npm-downloads-href]: https://npmjs.com/package/ant-design-x-vue
+[bundle-src]: https://img.shields.io/bundlephobia/minzip/ant-design-x-vue?style=flat
+[bundle-href]: https://bundlephobia.com/result?p=ant-design-x-vue
+[github-actions-src]: https://img.shields.io/github/actions/workflow/status/wzc520pyfm/ant-design-x-vue/ci.yml?branch=main&style=flat
+[github-actions-href]: https://github.com/wzc520pyfm/ant-design-x-vue/actions?query=workflow%3Aci
+[netlify-build-src]: https://img.shields.io/netlify/cb006e4e-afce-4c3e-9652-6f8a065b5b6e
+[license-src]: https://img.shields.io/github/license/wzc520pyfm/ant-design-x-vue?style=flat
+[license-href]: https://github.com/wzc520pyfm/ant-design-x-vue/blob/main/LICENSE

+ 175 - 0
docs/.vitepress/config.mts

@@ -0,0 +1,175 @@
+import { defineConfig } from 'vitepress'
+import VueMacros from 'unplugin-vue-macros/vite'
+import path from 'node:path';
+import { fileURLToPath, URL } from 'node:url'
+import { mdPlugin } from './config/plugins'
+import vueJsx from '@vitejs/plugin-vue-jsx';
+import { MarkdownTransform } from './plugins/markdown-transform';
+
+// https://vitepress.dev/reference/site-config
+export default defineConfig({
+  vite: {
+    css: {
+      preprocessorOptions: {
+        scss: {
+          silenceDeprecations: ['legacy-js-api'],
+        },
+      },
+    },
+    resolve: {
+      alias: [
+        {
+          find: /^ant-design-x-vue$/,
+          replacement: path.resolve(__dirname, '../../src')
+        },
+        {
+          find: /^dayjs\/plugin\/quarterOfYear$/,
+          replacement: 'dayjs/esm/plugin/quarterOfYear'
+        },
+        {
+          find: /^dayjs\/plugin\/weekYear$/,
+          replacement: 'dayjs/esm/plugin/weekYear'
+        },
+        {
+          find: /^dayjs\/plugin\/weekOfYear$/,
+          replacement: 'dayjs/esm/plugin/weekOfYear'
+        },
+        {
+          find: /^dayjs\/plugin\/localeData$/,
+          replacement: 'dayjs/esm/plugin/localeData'
+        },
+        {
+          find: /^dayjs\/plugin\/weekday$/,
+          replacement: 'dayjs/esm/plugin/weekday'
+        },
+        {
+          find: /^dayjs\/plugin\/advancedFormat$/,
+          replacement: 'dayjs/esm/plugin/advancedFormat'
+        },
+        {
+          find: /^dayjs\/plugin\/customParseFormat$/,
+          replacement: 'dayjs/esm/plugin/customParseFormat'
+        },
+        {
+          find: /^dayjs$/,
+          replacement: 'dayjs/esm'
+        },
+        {
+          find: /^.*\/VPHero\.vue$/,
+          replacement: fileURLToPath(
+            new URL('./vitepress/components/vp-hero.vue', import.meta.url)
+          )
+        }
+      ]
+    },
+    ssr: {
+      noExternal: ['ant-design-vue', '@ant-design/icons-vue'], // 避免打包为 CommonJS
+    },
+    build: {
+      rollupOptions: {
+        output: {
+          globals: {
+            'ant-design-vue': 'AntDesignVue',
+          },
+        },
+      },
+    },
+    plugins: [
+      VueMacros({
+        plugins: {
+          vueJsx: vueJsx(),
+        },
+        // 覆盖插件选项
+      }),
+      MarkdownTransform(),
+    ],
+  },
+  markdown: {
+    config: (md) => mdPlugin(md),
+  },
+  /* title: "Ant Design X Vue", */
+  description: "Ant Design X For Vue",
+  appearance: 'dark',
+  themeConfig: {
+    // https://vitepress.dev/reference/default-theme-config
+   /*  logo: 'https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*eco6RrQhxbMAAAAAAAAAAAAADgCCAQ/original', */
+    nav: [
+     /*  { text: '研发', link: '/development/introduce' },
+      { text: '组件', link: '/component/overview' }, */
+      { text: '演示', link: '/playground/independent' },
+      /* {
+        text: '更多',
+        items: [
+          { text: 'Ant Design X of React', link: 'https://x.ant.design/index-cn' },
+          { text: 'Ant Design Vue', link: 'https://www.antdv.com/components/overview-cn' },
+        ]
+      } */
+    ],
+   /*  outline: [2, 3], */
+
+    sidebar: {
+      /* '/development': [
+        {
+          text: 'Ant Design X of Vue',
+          link: '/development/introduce'
+        },
+      ],
+      '/component/': [
+        {
+          text: '总览',
+          link: '/component/overview'
+        },
+        {
+          text: '通用',
+          items: [
+            { text: 'Bubble 对话气泡框', link: '/component/bubble' },
+            { text: 'Conversations 管理对话', link: '/component/conversations' }
+          ]
+        },
+        {
+          text: '唤醒',
+          items: [
+            { text: 'Welcome 欢迎', link: '/component/welcome' },
+            { text: 'Prompts 提示集', link: '/component/prompts' }
+          ]
+        },
+        {
+          text: '表达',
+          items: [
+            { text: 'Sender 输入框', link: '/component/sender' },
+            { text: 'Attachments 输入附件', link: '/component/attachments' },
+            { text: 'Suggestion 快捷指令', link: '/component/suggestion' }
+          ]
+        },
+        {
+          text: '确认',
+          items: [
+            { text: 'ThoughtChain 思维链', link: '/component/thought-chain' }
+          ]
+        },
+        {
+          text: '工具',
+          items: [
+            { text: 'useXAgent 模型调度', link: '/component/use-x-agent' },
+            { text: 'useXChat 数据管理', link: '/component/use-x-chat' },
+            { text: 'XStream 流', link: '/component/x-stream' },
+            { text: 'XRequest 请求', link: '/component/x-request' },
+            { text: 'XProvider 全局化配置', link: '/component/x-provider' }
+          ]
+        },
+      ], */
+     /*  '/playground/': [
+        {
+          text: '样板间',
+          items: [
+            { text: '独立式', link: '/playground/independent' }
+          ]
+        },
+      ] */
+    },
+
+   /*  socialLinks: [
+      { icon: 'github', link: 'https://github.com/wzc520pyfm/ant-design-x-vue' }
+    ] */
+  }
+})

+ 7 - 0
docs/.vitepress/config/plugins.ts

@@ -0,0 +1,7 @@
+import mdContainer from 'markdown-it-container'
+import createDemoContainer from '../plugins/demo'
+import type MarkdownIt from 'markdown-it'
+
+export const mdPlugin = (md: MarkdownIt) => {
+  md.use(mdContainer, 'demo', createDemoContainer(md))
+}

+ 57 - 0
docs/.vitepress/plugins/demo.ts

@@ -0,0 +1,57 @@
+import path from 'path'
+import fs from 'fs'
+import { docRoot } from '../../../internal/build-utils/src'
+import type { MarkdownRenderer } from 'vitepress'
+
+interface ContainerOpts {
+  marker?: string | undefined
+  validate?(params: string): boolean
+  render?: MarkdownRenderer['renderer']['rules']['container']
+}
+
+function createDemoContainer(md: MarkdownRenderer): ContainerOpts {
+  return {
+    validate(params) {
+      return !!params.trim().match(/^demo\s*(.*)$/)
+    },
+
+    render(tokens, idx) {
+      const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/)
+      if (tokens[idx].nesting === 1 /* means the tag is opening */) {
+        const title = tokens[idx - 2]?.children?.[0].content
+        const description = m && m.length > 1 ? m[1] : ''
+        const sourceFileToken = tokens[idx + 2]
+        let source = ''
+        let sourceSetup = ''
+        const sourceFile = sourceFileToken.children?.[0].content ?? ''
+
+        if (sourceFileToken.type === 'inline') {
+          source = fs.readFileSync(
+            path.resolve(docRoot, 'examples', `${sourceFile}.vue`),
+            'utf-8'
+          )
+          sourceSetup = fs.readFileSync(
+            path.resolve(docRoot, 'examples-setup', `${sourceFile}.vue`),
+            'utf-8'
+          )
+        }
+        if (!source || !sourceSetup) throw new Error(`Incorrect source file: ${sourceFile}`)
+
+        return `<Demo source="${encodeURIComponent(
+          md.render(`\`\`\` ts\n${source}\`\`\``)
+        )}" source-setup="${encodeURIComponent(
+          md.render(`\`\`\` vue\n${sourceSetup}\`\`\``)
+        )}" path="${sourceFile}" raw-source="${encodeURIComponent(
+          source
+        )}" raw-source-setup="${encodeURIComponent(
+          sourceSetup
+        )}" description="${encodeURIComponent(md.render(description))}" title="${title}">
+  <template #source><ax-${sourceFile.replaceAll('/', '-')}/></template><template #source-setup><ax-${sourceFile.replaceAll('/', '-')}-setup/></template>`
+      } else {
+        return '</Demo>\n'
+      }
+    },
+  }
+}
+
+export default createDemoContainer

+ 119 - 0
docs/.vitepress/plugins/markdown-transform.ts

@@ -0,0 +1,119 @@
+import fs from 'fs'
+import path from 'path'
+import { camelize } from '@vue/shared'
+import glob from 'fast-glob'
+import { docRoot } from '../../../internal/build-utils/src'
+import type { Plugin } from 'vite'
+
+type Append = Record<'headers' | 'footers' | 'scriptSetups', string[]>
+
+let compPaths: string[]
+
+export function MarkdownTransform(): Plugin {
+  return {
+    name: 'antd-xv-md-transform',
+
+    enforce: 'pre',
+
+    async buildStart() {
+      const pattern = `/component`
+
+      compPaths = await glob(pattern, {
+        cwd: docRoot,
+        absolute: true,
+        onlyDirectories: true,
+      })
+    },
+
+    async transform(code, id) {
+      if (!id.endsWith('.md')) return
+      if (!id.includes('/component')) return
+
+      const componentId = path.basename(id, '.md')
+      const append: Append = {
+        headers: [],
+        footers: [],
+        scriptSetups: getExampleImports(componentId),
+      }
+
+      code = transformVpScriptSetup(code, append)
+
+      if (compPaths.some((compPath) => id.startsWith(compPath))) {
+        code = transformComponentMarkdown(id, componentId, code, append)
+      }
+
+      return combineMarkdown(
+        code,
+        [combineScriptSetup(append.scriptSetups), ...append.headers],
+        append.footers
+      )
+    },
+  }
+}
+
+const combineScriptSetup = (codes: string[]) =>
+  `\n<script setup>
+${codes.join('\n')}
+</script>
+`
+
+const combineMarkdown = (
+  code: string,
+  headers: string[],
+  footers: string[]
+) => {
+  const frontmatterEnds = code.indexOf('---\n\n')
+  const firstHeader = code.search(/\n#{1,6}\s.+/)
+  const sliceIndex =
+    firstHeader < 0
+      ? frontmatterEnds < 0
+        ? 0
+        : frontmatterEnds + 4
+      : firstHeader
+
+  if (headers.length > 0)
+    code =
+      code.slice(0, sliceIndex) + headers.join('\n') + code.slice(sliceIndex)
+  code += footers.join('\n')
+
+  return `${code}\n`
+}
+
+const vpScriptSetupRE = /<vp-script\s(.*\s)?setup(\s.*)?>([\s\S]*)<\/vp-script>/
+
+const transformVpScriptSetup = (code: string, append: Append) => {
+  const matches = code.match(vpScriptSetupRE)
+  if (matches) code = code.replace(matches[0], '')
+  const scriptSetup = matches?.[3] ?? ''
+  if (scriptSetup) append.scriptSetups.push(scriptSetup)
+  return code
+}
+
+const transformComponentMarkdown = (
+  id: string,
+  componentId: string,
+  code: string,
+  append: Append
+) => {
+  return code
+}
+
+const getExampleImports = (componentId: string) => {
+  const examplePath = path.resolve(docRoot, 'examples', componentId)
+  if (!fs.existsSync(examplePath)) return []
+  const files = fs.readdirSync(examplePath)
+  const imports: string[] = []
+
+  for (const item of files) {
+    if (!/\.vue$/.test(item)) continue
+    const file = item.replace(/\.vue$/, '')
+    const name = camelize(`Ax-${componentId}-${file}`)
+
+    imports.push(
+      `import ${name} from '../examples/${componentId}/${file}.vue'`,
+      `import ${name}Setup from '../examples-setup/${componentId}/${file}.vue'`
+    )
+  }
+
+  return imports
+}

+ 47 - 0
docs/.vitepress/theme/components/PreferenceSwitch.vue

@@ -0,0 +1,47 @@
+<script setup lang="tsx">
+import { useStorage } from '@vueuse/core';
+import { Switch, Descriptions, DescriptionsItem, theme } from 'ant-design-vue';
+import { onMounted, ref } from 'vue';
+
+defineOptions({ name: 'VpPreferenceSwitch' });
+
+const { token } = theme.useToken();
+
+const checked = ref(false);
+
+const preferLocal = useStorage('antdx-docs-preference', 'tsx')
+
+const triggerPreference = (prefer: 'tsx' | 'setup') => {
+  if (typeof localStorage === 'undefined') {
+    return () => {}
+  }
+  const classList = document.documentElement.classList
+  classList.remove('prefer-tsx', 'prefer-setup')
+  classList.add(`prefer-${prefer}`)
+  preferLocal.value = prefer
+};
+
+onMounted(() => {
+  if (preferLocal.value === 'tsx') {
+    checked.value = true
+  }
+});
+</script>
+
+<template>
+  <div style="margin-top: 16px;">
+    <Descriptions>
+      <DescriptionsItem label="风格偏好">
+        <Switch
+          v-model:checked="checked"
+          @change="(e) => triggerPreference(e ? 'tsx' : 'setup')"
+          :style="{
+            background: token.colorBgSpotlight,
+          }"
+          unCheckedChildren="setup"
+          checkedChildren="tsx"
+        />
+      </DescriptionsItem>
+    </Descriptions>
+  </div>
+</template>

+ 27 - 0
docs/.vitepress/theme/index.ts

@@ -0,0 +1,27 @@
+import { globals } from '../vitepress'
+import DefaultTheme from 'vitepress/theme'
+import type { Theme } from 'vitepress'
+import './style.css'
+import { computed, h } from 'vue'
+import { theme, XProvider } from 'ant-design-x-vue'
+import PreferenceSwitch from './components/PreferenceSwitch.vue'
+import { useData } from 'vitepress'
+
+const define = <T>(value: T): T => value
+
+export default define<Theme>({
+  extends: DefaultTheme,
+  Layout() {
+    const { isDark } = useData()
+    const algorithm = computed(() => isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm)
+
+    return h(XProvider, { theme: { algorithm: algorithm.value, } }, () => h(DefaultTheme.Layout, null, {
+      'sidebar-nav-before': () => h(PreferenceSwitch),
+    }));
+  },
+  enhanceApp: ({ app }) => {
+    globals.forEach(([name, Comp]) => {
+      app.component(name, Comp)
+    })
+  },
+})

+ 7 - 0
docs/.vitepress/theme/style.css

@@ -0,0 +1,7 @@
+:root {
+  --vp-border-color: rgba(5, 5, 5, 6%);
+}
+
+.dark {
+  --vp-border-color: rgba(253, 253, 253, 12%);
+}

+ 31 - 0
docs/.vitepress/vitepress/components/demo/vp-source-code.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    required: true,
+  },
+  source: {
+    type: String,
+    required: true,
+  },
+})
+
+const decoded = computed(() => {
+  return decodeURIComponent(props.source)
+})
+</script>
+
+<template>
+  <div v-show="visible" class="example-source-wrapper">
+    <div class="example-source" v-html="decoded" />
+  </div>
+</template>
+
+<style scoped lang="scss">
+:deep(.language-vue) {
+  margin: 0 !important;
+  border-radius: 0 !important;
+}
+</style>

+ 75 - 0
docs/.vitepress/vitepress/components/icons/hero.vue

@@ -0,0 +1,75 @@
+<template>
+  <svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="150 100 600 600"
+    width="900" height="900" preserveAspectRatio="xMidYMid meet"
+    style="width: 100%;height: 100%;transform: translate3d(0px, 0px, 0px);content-visibility: visible;">
+    <g clip-path="url(#__lottie_element_14)">
+      <g clip-path="url(#__lottie_element_16)" transform="matrix(1,0,0,1,-540,0)" opacity="1" style="display: block;"
+        class="">
+        <g transform="matrix(0.9333299994468689,0,0,0.9333299994468689,741.6007080078125,442.7997131347656)"
+          opacity="0.5" style="display: none;">
+          <g opacity="1" transform="matrix(1,0,0,1,0,0)">
+            <path stroke="url(#__lottie_element_56)" stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0"
+              stroke-miterlimit="4" stroke-opacity="1" stroke-width="1.2" d="M0 0"></path>
+          </g>
+        </g>
+        <g transform="matrix(0.8399999737739563,0,0,0.8399999737739563,772.43505859375,240.6540069580078)" opacity="0.5"
+          style="display: none;">
+          <g opacity="1" transform="matrix(1,0,0,1,0,0)">
+            <path stroke="url(#__lottie_element_52)" stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0"
+              stroke-miterlimit="4" stroke-opacity="1" stroke-width="1.2" d="M0 0"></path>
+          </g>
+        </g>
+        <g transform="matrix(0,0.7559999823570251,-0.7559999823570251,0,936.1370239257812,95.88749694824219)"
+          opacity="0.5" style="display: none;">
+          <g opacity="1" transform="matrix(1,0,0,1,0,0)">
+            <path stroke="url(#__lottie_element_48)" stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0"
+              stroke-miterlimit="4" stroke-opacity="1" stroke-width="1.2" d="M0 0"></path>
+          </g>
+        </g>
+        <g transform="matrix(0,0.680400013923645,-0.680400013923645,0,1180.6092529296875,120.86973571777344)"
+          opacity="0.5" style="display: none;">
+          <g opacity="1" transform="matrix(1,0,0,1,0,0)">
+            <path stroke="url(#__lottie_element_44)" stroke-linecap="butt" stroke-linejoin="miter" fill-opacity="0"
+              stroke-miterlimit="4" stroke-opacity="1" stroke-width="1.2" d="M0 0"></path>
+          </g>
+        </g>
+        <g class="png" style="display: block;"
+          transform="matrix(0.49984171986579895,0,0,0.49984171986579895,867.0557250976562,518.4012451171875)"
+          opacity="0.9990504329105607">
+          <image width="704px" height="180px" preserveAspectRatio="xMidYMid slice"
+            xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*sj5-SbD5N-QAAAAAAAAAAAAADgCCAQ/original">
+          </image>
+        </g>
+        <g mask="url(#__lottie_element_35_1)" style="display: block;">
+          <g class="png"
+            transform="matrix(0.5035207271575928,0,0,0.5035207271575928,848.6726684570312,153.64657592773438)"
+            opacity="0.6">
+            <image width="754px" height="752px" preserveAspectRatio="xMidYMid slice"
+              xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*4lqPTowypyoAAAAAAAAAAAAADgCCAQ/original">
+            </image>
+          </g>
+        </g>
+        <g class="png" style="display: block;" transform="matrix(0.5,0,0,0.5,816,154.97036743164062)" opacity="1">
+          <image width="896px" height="762px" preserveAspectRatio="xMidYMid slice"
+            xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*FwIEQom15DsAAAAAAAAAAAAADgCCAQ/original">
+          </image>
+        </g>
+        <g class="png" transform="matrix(0.5,0,0,0.5,948,247.97036743164062)" opacity="1" style="display: block;">
+          <image width="380px" height="376px" preserveAspectRatio="xMidYMid slice"
+            xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*k0oYSZQMoBwAAAAAAAAAAAAADgCCAQ/original">
+          </image>
+        </g>
+        <g class="png" style="display: block;" transform="matrix(0.5,0,0,0.5,1122,175.97036743164062)" opacity="1">
+          <image width="240px" height="568px" preserveAspectRatio="xMidYMid slice"
+            xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*RW04QJvg4nwAAAAAAAAAAAAADgCCAQ/original">
+          </image>
+        </g>
+        <g class="png" style="display: block;" transform="matrix(0.5,0,0,0.5,976,148.9708709716797)" opacity="1">
+          <image width="266px" height="82px" preserveAspectRatio="xMidYMid slice"
+            xlink:href="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*eEyaRY2tKwsAAAAAAAAAAAAADgCCAQ/original">
+          </image>
+        </g>
+      </g>
+    </g>
+  </svg>
+</template>

+ 185 - 0
docs/.vitepress/vitepress/components/semantic/preview.vue

@@ -0,0 +1,185 @@
+<script setup lang="ts">
+import { Col, Row, Typography, theme, Tag, Flex } from 'ant-design-vue';
+import { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue';
+import { XProvider } from 'ant-design-x-vue'
+import { useData } from 'vitepress'
+
+const { semantics, height} = defineProps<{
+  semantics: Array<{
+    name: string;
+    desc: string;
+    version?: string;
+  }>;
+  height?: number;
+}>()
+
+const MARK_BORDER_SIZE = 2
+
+const { token } = theme.useToken();
+
+const { isDark } = useData()
+const algorithm = computed(() => isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm)
+
+// ======================= Semantic =======================
+const getMarkClassName = (semanticKey: string) => `semantic-mark-${semanticKey}`
+
+const semanticClassNames = computed(() => {
+  const classNames: Record<string, string> = {};
+  semantics.forEach((semantic) => {
+    classNames[semantic.name] = getMarkClassName(semantic.name);
+  });
+
+
+  return classNames;
+});
+
+
+// ======================== Hover =========================
+const containerRef = useTemplateRef('container');
+
+let timer: ReturnType<typeof setTimeout> | null = null;
+
+const positionMotion = ref(false)
+const hoverSemantic = ref<string | null>(null)
+const markPos = ref<[number, number, number, number]>([0,0,0,0])
+
+watch(hoverSemantic, () => {
+  if(timer) {
+    clearTimeout(timer);
+  }
+  if (hoverSemantic.value) {
+    const targetClassName = getMarkClassName(hoverSemantic.value);
+    const targetElement = containerRef.value?.querySelector<HTMLElement>(`.${targetClassName}`);
+    const containerRect = containerRef.value?.getBoundingClientRect();
+    const targetRect = targetElement?.getBoundingClientRect();
+    markPos.value = [
+      (targetRect?.left || 0) - (containerRect?.left || 0),
+      (targetRect?.top || 0) - (containerRect?.top || 0),
+      targetRect?.width || 0,
+      targetRect?.height || 0,
+    ]
+    timer = setTimeout(() => {
+      positionMotion.value = true
+    }, 10);
+  } else {
+    timer = setTimeout(() => {
+      positionMotion.value = false
+    }, 500);
+  }
+})
+
+onBeforeUnmount(() => {
+  if (timer) {
+    clearTimeout(timer);
+    timer = null;
+  }
+})
+
+</script>
+
+<template>
+  <div class="semantic-container" ref="container">
+    <Row :style="{minHeight: height}">
+      <Col :span="18" class="col-wrap">
+        <XProvider :theme="{
+          algorithm
+        }">
+          <slot :classNames="semanticClassNames" />
+        </XProvider>
+      </Col>
+      <Col :span="6">
+          <ul class="list-wrap">
+            <li
+                v-for="semantic in semantics"
+                :key="semantic.name"
+                class="list-item"
+                @mouseenter="hoverSemantic = semantic.name"
+                @mouseleave="hoverSemantic = null"
+              >
+                <Flex vertical gap="small">
+                  <Flex gap="small" align="center">
+                    <Typography.Title :level="5" :style="{ margin: 0 }">
+                      {{ semantic.name }}
+                    </Typography.Title>
+                    <Tag v-if="semantic.version" color="blue">{{ semantic.version }}</Tag>
+                  </Flex>
+                  <Typography.Paragraph :style="{margin: 0, fontSize: token.fontSizeSM + 'px' }">
+                    {{ semantic.desc }}
+                  </Typography.Paragraph>
+                </Flex>
+              </li>
+          </ul>
+        </Col>
+    </Row>
+    <div
+      :class="`marker ${hoverSemantic ? 'marker-active' : 'marker-not-active'} ${positionMotion ? 'marker-motion' : 'marker-not-motion'}`"
+    />
+  </div>
+</template>
+
+<style scoped lang="scss">
+.semantic-container {
+  border: 1px solid var(--vp-border-color);
+  border-radius: 8px;
+  position: relative;
+  overflow: hidden;
+  .col-wrap {
+    border-right: 1px solid v-bind('token.colorBorderSecondary');
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: v-bind('token.paddingMD + "px"');
+    overflow: hidden;
+  }
+  .list-wrap {
+    display: flex;
+    flex-direction: column;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+  }
+  .list-item {
+    margin-top: 0;
+    cursor: pointer;
+    padding: v-bind('token.paddingSM + "px"');
+    transition: background-color v-bind('token.motionDurationFast') ease;
+    &:hover {
+      background-color: v-bind('token.controlItemBgHover');
+    }
+    &:not(:first-of-type) {
+      border-top: 1px solid v-bind('token.colorBorderSecondary');
+    }
+  }
+
+  .marker {
+    position: absolute;
+    border: v-bind('MARK_BORDER_SIZE + "px"') solid v-bind('token.colorWarning');
+    box-sizing: border-box;
+    z-index: 999999;
+    box-shadow: 0 0 0 1px #fff;
+    pointer-events: none;
+    inset-inline-start: v-bind('markPos[0] - MARK_BORDER_SIZE + "px"');
+    top: v-bind('markPos[1] - MARK_BORDER_SIZE + "px"');
+    width: v-bind('markPos[2] + MARK_BORDER_SIZE * 2 + "px"');
+    height: v-bind('markPos[3] + MARK_BORDER_SIZE * 2 + "px"');
+  };
+
+  .marker-active {
+    opacity: 1;
+  }
+
+  .marker-not-active {
+    opacity: 0;
+  }
+
+  .marker-motion {
+    transition:
+      opacity v-bind('token.motionDurationSlow') ease,
+      all v-bind('token.motionDurationSlow') ease;
+  }
+  .marker-not-motion {
+    transition: opacity v-bind('token.motionDurationSlow') ease;
+  }
+}
+</style>

+ 217 - 0
docs/.vitepress/vitepress/components/vp-demo.vue

@@ -0,0 +1,217 @@
+<script setup lang="ts">
+import { computed, getCurrentInstance, ref, toRef } from 'vue'
+import { isClient, useClipboard, useStorage, useToggle } from '@vueuse/core'
+import { App, Divider, Tooltip, theme } from 'ant-design-vue'
+import { XProvider } from 'ant-design-x-vue'
+import { CaretUpFilled, CodepenOutlined, ThunderboltOutlined, FunctionOutlined } from '@ant-design/icons-vue'
+// import { useLang } from '../composables/lang'
+import { useSourceCode } from '../composables/source-code'
+import { usePlayground } from '../composables/use-playground'
+// import demoBlockLocale from '../../i18n/component/demo-block.json'
+import SourceCode from './demo/vp-source-code.vue'
+import { useData } from 'vitepress'
+
+const props = defineProps<{
+  source: string
+  sourceSetup: string
+  path: string
+  rawSource: string
+  rawSourceSetup: string
+  description: string
+  title: string
+}>()
+
+const vm = getCurrentInstance()!
+
+const prefer = useStorage('antdx-docs-preference', 'tsx');
+
+const { isDark } = useData()
+
+const algorithm = computed(() => isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm)
+
+const { copy, isSupported } = useClipboard({
+  source: () => decodeURIComponent(prefer.value === 'tsx' ? props.rawSource : props.rawSourceSetup),
+  read: false,
+})
+
+const [sourceVisible, toggleSourceVisible] = useToggle()
+// const lang = useLang()
+const demoSourceUrl = useSourceCode(toRef(props, 'path'))
+
+const sourceCodeRef = ref<HTMLButtonElement>()
+
+// const locale = computed(() => demoBlockLocale[lang.value])
+const locale = computed(() => ({}));
+const decodedDescription = computed(() => decodeURIComponent(props.description))
+const { onStackblitzPlayBtnClick } = usePlayground({
+  title: props.title,
+  rawSource: () => prefer.value === 'tsx' ? props.rawSource : props.rawSourceSetup,
+  path: props.path,
+})
+
+const onSourceVisibleKeydown = (e: KeyboardEvent) => {
+  if (['Enter', 'Space'].includes(e.code)) {
+    e.preventDefault()
+    toggleSourceVisible(false)
+    sourceCodeRef.value?.focus()
+  }
+}
+
+const copyCode = async () => {
+  const { $message } = vm.appContext.config.globalProperties
+  if (!isSupported) {
+    // $message.error(locale.value['copy-error'])
+  }
+  try {
+    await copy()
+    // $message.success(locale.value['copy-success'])
+  } catch (e: any) {
+    // $message.error(e.message)
+  }
+}
+</script>
+
+<template>
+  <!-- danger here DO NOT USE INLINE SCRIPT TAG -->
+ <!--  <div style="margin-top: 16px; margin-bottom: 16px;" v-html="decodedDescription" /> -->
+
+  <XProvider :theme="{
+    algorithm,
+  }">
+
+    <div class="example">
+      <div class="example-showcase">
+        <template v-if="prefer === 'tsx'">
+          <slot name="source" />
+        </template>
+        <template v-if="prefer === 'setup'">
+          <slot name="source-setup" />
+        </template>
+      </div>
+
+     <!--  <Divider style="margin: 0;" /> -->
+
+      <!-- <div class="op-btns"> -->
+       <!--  <Tooltip>
+          <template #title>在 Stackblitz 中打开</template>
+          <ThunderboltOutlined tabindex="0" role="link" class="op-btn" @click="onStackblitzPlayBtnClick"
+            @keydown.prevent.enter="onStackblitzPlayBtnClick" @keydown.prevent.space="onStackblitzPlayBtnClick" />
+        </Tooltip> -->
+        <!-- <Tooltip>
+        <template #title>在 CodePen 中打开</template>
+        <CodepenOutlined
+          tabindex="0"
+          role="link"
+          class="op-btn"
+          @click="onPlaygroundClick"
+          @keydown.prevent.enter="onPlaygroundClick"
+          @keydown.prevent.space="onPlaygroundClick"
+        />
+      </Tooltip> -->
+        <!-- <Tooltip>
+          <template #title>查看源代码</template>
+          <button ref="sourceCodeRef" :aria-label="sourceVisible ? '隐藏源代码' : '查看源代码'
+            " class="op-btn" @click="toggleSourceVisible()">
+            <FunctionOutlined />
+          </button>
+        </Tooltip> -->
+      <!-- </div> -->
+
+      <Transition name="fade-in-linear">
+        <SourceCode :visible="sourceVisible" :source="prefer === 'tsx' ? source : sourceSetup" />
+      </Transition>
+
+      <Transition name="fade-in-linear">
+        <div v-show="sourceVisible" class="example-float-control" tabindex="0" role="button"
+          @click="toggleSourceVisible(false)" @keydown="onSourceVisibleKeydown">
+          <CaretUpFilled />
+          <span>隐藏源代码</span>
+        </div>
+      </Transition>
+    </div>
+  </XProvider>
+</template>
+
+<style lang="scss">
+// reset vitepress style
+.example {
+  .example-showcase {
+    ul {
+      list-style: none;
+    }
+  }
+}
+</style>
+
+<style scoped lang="scss">
+.example {
+  
+ // border: 1px solid var(--vp-border-color);
+  border-radius: 8px;
+
+  .example-showcase {
+    padding: 1.5rem;
+    margin: 0.5px;
+
+    :deep(table) {
+      margin: 0;
+
+      tr {
+        border: unset;
+        background-color: unset;
+
+        td {
+          border: unset;
+        }
+      }
+    }
+  }
+
+  .op-btns {
+    padding: 0.5rem;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    height: 2.5rem;
+
+    .op-btn {
+      margin: 0 0.5rem;
+      cursor: pointer;
+      transition: 0.2s;
+    }
+  }
+
+  &-float-control {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-top: 1px solid var(--vp-border-color);
+    height: 44px;
+    box-sizing: border-box;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    margin-top: -1px;
+    cursor: pointer;
+    position: sticky;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 10;
+
+    span {
+      font-size: 14px;
+      margin-left: 10px;
+    }
+  }
+}
+
+.fade-in-linear-enter-active,
+.fade-in-linear-leave-active {
+  transition: opacity 0.2s linear;
+}
+
+.fade-in-linear-enter-from,
+.fade-in-linear-leave-to {
+  opacity: 0;
+}
+</style>

+ 336 - 0
docs/.vitepress/vitepress/components/vp-hero.vue

@@ -0,0 +1,336 @@
+<script setup lang="ts">
+import { type Ref, inject } from 'vue'
+import type { DefaultTheme } from 'vitepress/theme'
+import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
+import Hero from './icons/hero.vue';
+
+export interface HeroAction {
+  theme?: 'brand' | 'alt'
+  text: string
+  link: string
+  target?: string
+  rel?: string
+}
+
+defineProps<{
+  name?: string
+  text?: string
+  tagline?: string
+  image?: DefaultTheme.ThemeableImage
+  actions?: HeroAction[]
+}>()
+
+const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
+</script>
+
+<template>
+  <div class="VPHero" :class="{ 'has-image': image || heroImageSlotExists }">
+    <div class="container">
+      <div class="main">
+        <slot name="home-hero-info-before" />
+        <slot name="home-hero-info">
+          <h1 v-if="name" class="name">
+            <span v-html="name" class="clip"></span>
+          </h1>
+          <p v-if="text" v-html="text" class="text"></p>
+          <p v-if="tagline" v-html="tagline" class="tagline"></p>
+        </slot>
+        <slot name="home-hero-info-after" />
+
+        <div v-if="actions" class="actions">
+          <div v-for="action in actions" :key="action.link" class="action">
+            <VPButton
+              tag="a"
+              size="medium"
+              :theme="action.theme"
+              :text="action.text"
+              :href="action.link"
+              :target="action.target"
+              :rel="action.rel"
+            />
+          </div>
+        </div>
+        <slot name="home-hero-actions-after" />
+      </div>
+
+      <div class="image">
+        <div class="image-container">
+          <div class="image-bg" />
+          <slot name="home-hero-image">
+            <Hero />
+          </slot>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.VPHero {
+  margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);
+  padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px;
+}
+
+@media (min-width: 640px) {
+  .VPHero {
+    padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px;
+  }
+}
+
+@media (min-width: 960px) {
+  .VPHero {
+    padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px;
+  }
+}
+
+.container {
+  display: flex;
+  flex-direction: column;
+  margin: 0 auto;
+  max-width: 1152px;
+}
+
+@media (min-width: 960px) {
+  .container {
+    flex-direction: row;
+  }
+}
+
+.main {
+  position: relative;
+  z-index: 10;
+  order: 2;
+  flex-grow: 1;
+  flex-shrink: 0;
+}
+
+.VPHero.has-image .container {
+  text-align: center;
+}
+
+@media (min-width: 960px) {
+  .VPHero.has-image .container {
+    text-align: left;
+  }
+}
+
+@media (min-width: 960px) {
+  .main {
+    order: 1;
+    width: calc((100% / 3) * 2);
+  }
+
+  .VPHero.has-image .main {
+    max-width: 592px;
+  }
+}
+
+.name,
+.text {
+  max-width: 392px;
+  letter-spacing: -0.4px;
+  line-height: 40px;
+  font-size: 32px;
+  font-weight: 700;
+  white-space: pre-wrap;
+}
+
+.VPHero.has-image .name,
+.VPHero.has-image .text {
+  margin: 0 auto;
+}
+
+.name {
+  color: var(--vp-home-hero-name-color);
+}
+
+.clip {
+  background: var(--vp-home-hero-name-background);
+  -webkit-background-clip: text;
+  background-clip: text;
+  -webkit-text-fill-color: var(--vp-home-hero-name-color);
+}
+
+@media (min-width: 640px) {
+  .name,
+  .text {
+    max-width: 576px;
+    line-height: 56px;
+    font-size: 48px;
+  }
+}
+
+@media (min-width: 960px) {
+  .name,
+  .text {
+    line-height: 64px;
+    font-size: 56px;
+  }
+
+  .VPHero.has-image .name,
+  .VPHero.has-image .text {
+    margin: 0;
+  }
+}
+
+.tagline {
+  padding-top: 8px;
+  max-width: 392px;
+  line-height: 28px;
+  font-size: 18px;
+  font-weight: 500;
+  white-space: pre-wrap;
+  color: var(--vp-c-text-2);
+}
+
+.VPHero.has-image .tagline {
+  margin: 0 auto;
+}
+
+@media (min-width: 640px) {
+  .tagline {
+    padding-top: 12px;
+    max-width: 576px;
+    line-height: 32px;
+    font-size: 20px;
+  }
+}
+
+@media (min-width: 960px) {
+  .tagline {
+    line-height: 36px;
+    font-size: 24px;
+  }
+
+  .VPHero.has-image .tagline {
+    margin: 0;
+  }
+}
+
+.actions {
+  display: flex;
+  flex-wrap: wrap;
+  margin: -6px;
+  padding-top: 24px;
+}
+
+.VPHero.has-image .actions {
+  justify-content: center;
+}
+
+@media (min-width: 640px) {
+  .actions {
+    padding-top: 32px;
+  }
+}
+
+@media (min-width: 960px) {
+  .VPHero.has-image .actions {
+    justify-content: flex-start;
+  }
+}
+
+.action {
+  flex-shrink: 0;
+  padding: 6px;
+}
+
+.image {
+  order: 1;
+  margin: -76px -24px -48px;
+}
+
+@media (min-width: 640px) {
+  .image {
+    margin: -108px -24px -48px;
+  }
+}
+
+@media (min-width: 960px) {
+  .image {
+    flex-grow: 1;
+    order: 2;
+    margin: 0;
+    min-height: 100%;
+  }
+}
+
+.image-container {
+  position: relative;
+  margin: 0 auto;
+  width: 320px;
+  height: 320px;
+}
+
+@media (min-width: 640px) {
+  .image-container {
+    width: 392px;
+    height: 392px;
+  }
+}
+
+@media (min-width: 960px) {
+  .image-container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    /*rtl:ignore*/
+    transform: translate(-32px, -32px);
+  }
+}
+
+.image-bg {
+  position: absolute;
+  top: 50%;
+  /*rtl:ignore*/
+  left: 50%;
+  border-radius: 50%;
+  width: 192px;
+  height: 192px;
+  background-image: var(--vp-home-hero-image-background-image);
+  filter: var(--vp-home-hero-image-filter);
+  /*rtl:ignore*/
+  transform: translate(-50%, -50%);
+}
+
+@media (min-width: 640px) {
+  .image-bg {
+    width: 256px;
+    height: 256px;
+  }
+}
+
+@media (min-width: 960px) {
+  .image-bg {
+    width: 320px;
+    height: 320px;
+  }
+}
+
+:deep(.image-src) {
+  position: absolute;
+  top: 50%;
+  /*rtl:ignore*/
+  left: 50%;
+  max-width: 192px;
+  max-height: 192px;
+  /*rtl:ignore*/
+  transform: translate(-50%, -50%);
+}
+
+@media (min-width: 640px) {
+  :deep(.image-src) {
+    max-width: 256px;
+    max-height: 256px;
+  }
+}
+
+@media (min-width: 960px) {
+  :deep(.image-src) {
+    max-width: 320px;
+    max-height: 320px;
+  }
+}
+</style>

+ 17 - 0
docs/.vitepress/vitepress/components/vp-semantic.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { computed, defineAsyncComponent } from 'vue'
+
+const { component } = defineProps<{
+  component: string
+}>()
+
+const normalizedComponent = computed(() => component.slice(0, 1).toLowerCase() + component.slice(1))
+
+const SemanticComponent = defineAsyncComponent(() => import(`../../../semantics/${normalizedComponent.value}.vue`))
+
+
+</script>
+
+<template>
+  <SemanticComponent />
+</template>

+ 22 - 0
docs/.vitepress/vitepress/composables/source-code.ts

@@ -0,0 +1,22 @@
+import { computed } from 'vue'
+import { useData } from 'vitepress'
+import { createGitHubUrl } from '../utils'
+
+import type { Ref } from 'vue'
+
+export const useSourceCode = (path: Ref<string>) => {
+  const { theme } = useData()
+
+  const demoUrl = computed(() => {
+    const {
+      repo,
+      docsDir = '',
+      docsBranch = 'dev',
+      docsRepo = repo,
+    } = theme.value
+
+    return createGitHubUrl(docsRepo, docsDir, docsBranch, path.value)
+  })
+
+  return demoUrl
+}

+ 148 - 0
docs/.vitepress/vitepress/composables/use-playground.ts

@@ -0,0 +1,148 @@
+import stackblitzSdk from '@stackblitz/sdk';
+import type { Project as StackBlitzProject } from '@stackblitz/sdk';
+import pkg from '../../../../package.json' assert { type: 'json' };
+import { isClient, MaybeRefOrGetter } from '@vueuse/core'
+import { computed, toValue } from 'vue';
+
+const pkgDependencyList = {
+  ...pkg.dependencies,
+  ...pkg.peerDependencies
+}
+
+interface Playground {
+  title: MaybeRefOrGetter<string>;
+  rawSource: MaybeRefOrGetter<string>;
+  path: MaybeRefOrGetter<string>;
+}
+
+export function usePlayground({ title, rawSource, path }: Playground) {
+  const dependencies = computed(() => toValue(rawSource).split(/\n/).reduce<Record<PropertyKey, string>>(
+    (acc, line) => {
+      const matches = line.match(/import .+? from '(.+)';\r?$/);
+      if (matches?.[1]) {
+        const paths = matches[1].split('/');
+        const dep = paths[0].startsWith('@') ? `${paths[0]}/${paths[1]}` : paths[0];
+        acc[dep] ??= pkgDependencyList[dep] ?? 'latest';
+      }
+      return acc;
+    },
+    {
+      'ant-design-vue': pkgDependencyList['ant-design-vue'],
+      'ant-design-x-vue': pkg.version
+    },
+  ));
+
+  const html = `<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>
+`
+  const mainTsContent = `import { createApp } from 'vue'
+import './style.css'
+import Demo from './Demo.vue'
+
+createApp(Demo).mount('#app')
+`
+  const styleCssContent = ``
+
+  const demoVueContent = computed(() => `${decodeURIComponent(toValue(rawSource))}`)
+
+  const tsconfig = {
+    compilerOptions: {
+      target: 'esnext',
+      module: 'esnext',
+      esModuleInterop: true,
+      moduleResolution: 'bundler',
+      jsx: "preserve",
+      jsxImportSource: "vue",
+    },
+  };
+
+  const packageJson = computed(() => ({
+    "name": `${toValue(path).replaceAll('/', '-')}`,
+    "private": true,
+    "version": "0.0.0",
+    "type": "module",
+    "scripts": {
+      "dev": "vite",
+      "build": "vue-tsc -b && vite build",
+      "preview": "vite preview"
+    },
+    "dependencies": {
+      ...dependencies.value
+    },
+    "devDependencies": {
+      "@vitejs/plugin-vue-jsx": pkg.devDependencies['@vitejs/plugin-vue-jsx'],
+      "@vitejs/plugin-vue": pkg.devDependencies['@vitejs/plugin-vue'],
+      "typescript": pkg.devDependencies.typescript,
+      "vite": pkg.devDependencies.vite,
+      "vue-tsc": pkg.devDependencies['vue-tsc'],
+      'unplugin-vue-macros': pkg.devDependencies['unplugin-vue-macros']
+    }
+  }))
+
+  const viteTsContent = `import { defineConfig } from 'vite'
+import VueMacros from 'unplugin-vue-macros/vite'
+import vue from '@vitejs/plugin-vue';
+import vueJsx from '@vitejs/plugin-vue-jsx';
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+    VueMacros({
+      plugins: {
+        vue: vue(),
+        vueJsx: vueJsx(),
+      }
+    }),
+  ],
+})
+`
+  const vueEnvTsContent = `declare module '*.vue' {
+  import { DefineComponent, MaybeRefOrGetter, toValue } from 'vue';
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+  const component: DefineComponent<{}, {}, any>;
+
+  export default component;
+}
+`
+
+
+  const stackblitzPrefillConfig: StackBlitzProject = {
+    title: `${toValue(title)} - antd-design-x-vue@${pkg.version}`,
+    template: 'node',
+    description: '',
+    files: {
+      [`src/style.css`]: styleCssContent,
+      [`src/main.ts`]: mainTsContent,
+      [`src/Demo.vue`]: demoVueContent.value,
+      'index.html': html,
+      'tsconfig.json': JSON.stringify(tsconfig, null, 2),
+      'package.json': JSON.stringify(packageJson.value, null, 2),
+      'vite.config.ts': viteTsContent,
+      'vue.env.d.ts': vueEnvTsContent
+    },
+  };
+
+  const onStackblitzPlayBtnClick = () => {
+    if (!isClient) {
+      return
+    }
+    stackblitzSdk.openProject(stackblitzPrefillConfig, {
+      openFile: [`src/Demo.vue`],
+    });
+  }
+
+  return {
+    onStackblitzPlayBtnClick
+  }
+}

+ 8 - 0
docs/.vitepress/vitepress/index.ts

@@ -0,0 +1,8 @@
+import type { Component } from 'vue'
+import VPDemo from './components/vp-demo.vue'
+import VPSemantic from './components/vp-semantic.vue'
+
+export const globals: [string, Component][] = [
+  ['Demo', VPDemo],
+  ['VpSemantic', VPSemantic],
+]

+ 18 - 0
docs/.vitepress/vitepress/utils/index.ts

@@ -0,0 +1,18 @@
+import { isExternal } from 'vitepress/dist/client/shared.js'
+
+const endingSlashRE = /\/$/
+export function createGitHubUrl(
+  docsRepo: string,
+  docsDir: string,
+  docsBranch: string,
+  path: string,
+  folder = 'examples/',
+  ext = '.vue'
+) {
+  const base = isExternal(docsRepo)
+    ? docsRepo
+    : `https://github.com/${docsRepo}`
+  return `${base.replace(endingSlashRE, '')}/edit/${docsBranch}/${
+    docsDir ? `${docsDir.replace(endingSlashRE, '')}/` : ''
+  }${folder || ''}${path}${ext || ''}`
+}

+ 106 - 0
docs/component/attachments.md

@@ -0,0 +1,106 @@
+
+# Attachments 输入附件
+
+用于展示一组附件信息集合。
+
+## 何时使用
+
+Attachments 组件用于需要展示一组附件信息集合的场景。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法,可以通过 `getDropContainer` 支持拖拽上传。
+
+attachments/basic
+
+:::
+
+### 占位信息
+
+:::demo 修改占位信息。
+
+attachments/placeholder
+
+:::
+
+### 超出样式
+
+:::demo 控制附件超出区域长度时的展示方式。
+
+attachments/overflow
+
+:::
+
+### 组合示例
+
+:::demo 配合 Sender.Header 使用,在对话中插入附件。
+
+attachments/with-sender
+
+:::
+
+### 文件卡片
+
+:::demo 单独的文件卡片,用于一些展示场景。
+
+attachments/files
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props)。 -->
+
+### Attachments Props
+
+继承 antdv [Upload](https://www.antdv.com/components/upload-cn) 属性。
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| classNames | 自定义样式类名,[见下](#semantic-dom) | Record<string, string> | - | - |
+| disabled | 是否禁用 | boolean | false | - |
+| getDropContainer | 设置拖拽时,可以释放文件的区域 | () => HTMLElement | - | - |
+| items | 附件列表,同 Upload `fileList` | Attachment[] | - | - |
+| overflow | 文件列表超出时样式 | 'wrap' \| 'scrollX' \| 'scrollY' | - | - |
+| placeholder | 没有文件时的占位信息 | PlaceholderType \| ((type: 'inline' \| 'drop') => PlaceholderType) | - | - |
+| rootClassName | 根节点的样式类名 | string | - | - |
+| rootStyle | 根节点的样式对象 | CSSProperties | - | - |
+| styles | 自定义样式对象,[见下](#semantic-dom) | Record<string, CSSProperties> | - | - |
+
+```tsx | pure
+interface PlaceholderType {
+  icon?: VNode;
+  title?: VNode | string;
+  description?: VNode | string;
+}
+```
+
+### Attachments Slots
+
+| 插槽名 | 说明 | 类型 |
+| --- | --- | --- |
+| placeholder | 没有文件时的占位信息 | \{ type: "inline" \| "drop" \} |
+
+### Attachments Expose
+
+| 属性          | 说明             | 类型                 | 版本 |
+| ------------- | ---------------- | -------------------- | ---- |
+| nativeElement | 获取原生节点     | HTMLElement          | -    |
+| upload        | 手工调用上传文件 | (file: File) => void | -    |
+
+### Attachments.FileCard Props
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| item | 附件,同 Upload `UploadFile` | Attachment | - | - |
+| onRemove | 附件移除时的回调函数 | (item: Attachment) => void | - | - |
+
+## Semantic DOM
+
+<vp-semantic component="Attachments"></vp-semantic>
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="Prompts"></ComponentTokenTable> -->

+ 158 - 0
docs/component/bubble.md

@@ -0,0 +1,158 @@
+
+# Bubble 对话气泡
+
+用于聊天的气泡组件。
+
+## 何时使用
+
+常用于聊天的时候。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+bubble/basic
+
+:::
+
+### 支持位置和头像
+
+:::demo 通过 `avatar` 设置自定义头像,通过 `placement` 设置位置,提供了 `start`、`end` 两个选项。
+
+bubble/avatar-and-placement
+
+:::
+
+### 头和尾
+
+:::demo 通过 `header` 和 `footer` 属性设置气泡的头部和底部。
+
+bubble/header-and-footer
+
+:::
+
+### 加载中
+
+:::demo 通过 `loading` 属性控制加载状态。
+
+bubble/loading
+
+:::
+
+### 打字效果
+
+:::demo 通过设置 `typing` 属性,开启打字效果。更新 `content` 如果是之前的子集,则会继续输出,否则会重新输出。
+
+bubble/typing
+
+:::
+
+### 自定义渲染
+
+:::demo 配合 `markdown-it` 实现自定义渲染内容。
+
+bubble/markdown
+
+:::
+
+### 变体
+
+:::demo 通过 `variant` 属性设置气泡的样式变体。
+
+bubble/variant
+
+:::
+
+### 形状
+
+:::demo 通过 `shape` 属性设置气泡的形状。
+
+bubble/shape
+
+:::
+
+### 气泡列表
+
+:::demo 预设样式的气泡列表,支持自动滚动。使用 `roles` 设置气泡默认属性。
+
+bubble/list
+
+:::
+
+### 语义化自定义
+
+:::demo 示例通过语义化以及加载定制,来调整气泡效果。
+
+bubble/bubble-custom
+
+:::
+
+### 自定义列表内容
+
+:::demo 自定义气泡列表内容,这对于个性化定制场景非常有用。
+
+bubble/list-custom
+
+:::
+
+### 使用 GPT-Vis 渲染图表 (no support)
+
+@antv/GPT-Vis 仅支持React。
+
+:::demo 配合 @antv/GPT-Vis 实现大模型输出的图表渲染,支持模型流式输出。
+
+bubble/gpt-vis
+
+:::
+
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### Bubble
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| avatar | 展示头像 | VNode | - |  |
+| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - |  |
+| content | 聊天内容 | string | - |  |
+| footer | 底部内容 | VNode | - |  |
+| header | 头部内容 | VNode | - |  |
+| loading | 聊天内容加载状态 | boolean | - |  |
+| placement | 信息位置 | `start` \| `end` | `start` |  |
+| shape | 气泡形状 | `round` \| `corner` | - |  |
+| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - |  |
+| typing | 设置聊天内容打字动画 | boolean \| \{ step?: number, interval?: number \} | false |  |
+| variant | 气泡样式变体 | `filled` \| `borderless` \| `outlined` \| `shadow` | `filled` |  |
+| loadingRender | 自定义渲染加载态内容 | () => VNode | - |  |
+| messageRender | 自定义渲染内容 | (content?: string) => VNode | - |  |
+| onTypingComplete | 打字效果完成时的回调,如果没有设置 typing 将在渲染时立刻触发 | () => void | - |  |
+
+### Bubble Slots
+
+| 插槽名 | 说明 | 类型 |
+| --- | --- | --- |
+| avatar | 头像 | - |
+| header | 头部面板 | - |
+| footer | 底部内容 | - |
+| loading | loading占位 | - |
+| message | 消息内容 | \{ content: string \} |
+
+### Bubble.List
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| autoScroll | 当内容更新时,自动滚动到最新位置。如果用户滚动,则会暂停自动滚动。 | boolean | true |  |
+| items | 气泡数据列表 | (BubbleProps & { key?: string \| number, role?: string })[] | - |  |
+| roles | 设置气泡默认属性,`items` 中的 `role` 会进行自动对应 | Record<string, BubbleProps> \| (bubble) => BubbleProps | - |  |
+
+## Semantic DOM
+
+<vp-semantic component="Bubble"></vp-semantic>
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="Bubble"></ComponentTokenTable> -->

+ 89 - 0
docs/component/conversations.md

@@ -0,0 +1,89 @@
+
+# Conversations 管理对话
+
+用于承载用户侧发送的历史对话列表。
+
+## 何时使用
+
+* 需要对多个会话进行管理
+* 查看历史会话列表
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+conversations/basic
+
+:::
+
+### 会话操作
+
+:::demo 配合 `menu` 属性,配置操作菜单
+
+conversations/with-menu
+
+:::
+
+### 受控模式
+
+:::demo 使用 `activeKey`、`onChange` 属性,控制当前选中的会话
+
+conversations/controlled-mode
+
+:::
+
+### 分组展示
+
+:::demo 使用 `groupable` 属性开启分组,开启后默认按 [Conversation.group](#) 字段分组
+
+conversations/group
+
+:::
+
+### 分组排序
+
+:::demo 通过 `groupable.sort` 属性对分组排序, 通过 `groupable.title` 自定义渲染分组
+
+conversations/group-sort
+
+:::
+
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### ConversationsProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| activeKey | 当前选中的值 | string | - | - |
+| defaultActiveKey | 默认选中值 | string | - | - |
+| items | 会话列表数据源 | `Conversation`[] | - | - |
+| onActiveChange | 选中变更回调 | (value: string) => void | - | - |
+| menu | 会话操作菜单 | MenuProps \| ((value: `Conversation`) => MenuProps) | - | - |
+| groupable | 是否支持分组, 开启后默认按 `Conversation.group` 字段分组 | boolean \| GroupableProps | - | - |
+| styles | 语义化结构 style | Record<'item', CSSProperties> | - | - |
+| classNames | 语义化结构 className | Record<'item', string> | - | - |
+
+### Conversation
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| key | 唯一标识 | string | - | - |
+| label | 会话名称 | VNode \| string | - | - |
+| timestamp | 会话时间戳 | number | - | - |
+| group | 会话分组类型,与 `ConversationsProps.groupable` 联动 | string | - | - |
+| icon | 会话图标 | VNode \| string | - | - |
+| disabled | 是否禁用 | boolean | - | - |
+
+### GroupableProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| `sort` | 分组排序函数 | (a: string, b: string) => number | - | - |
+| `title` | 自定义渲染组件 | ((group: string, info: { components: { GroupTitle: typeof GroupTitle } }) => VNode) | - | - |
+
+## 主题变量(Design Token)

+ 4 - 0
docs/component/overview.md

@@ -0,0 +1,4 @@
+# 总览
+
+`ant-design-x-vue` 是 `@ant-design/x`的 Vue 实现,是一个专注于 Vue 生态的先进 AI 组件库,旨在简化与人工智能集成的开发过程。我们的库包括高度定制化的 AI 组件,允许开发者轻松地将对话 AI 集成到他们的应用中。除了丰富的 UI 组件,`ant-design-x-vue` 还提供了一揽子 API 解决方案,支持开发者通过令牌认证直接接入现有 AI 服务,无缝衔接与 AI 的对话和交互。无论是建立智能聊天应用、提升用户交互体验还是加快 AI 能力的集成,`ant-design-x-vue` 都是 Vue 开发者进入 AI 世界的理想伙伴。
+

+ 107 - 0
docs/component/prompts.md

@@ -0,0 +1,107 @@
+
+# Prompts 提示集
+
+用于显示一组与当前上下文相关的预定义的问题或建议。
+
+## 何时使用
+
+Prompts 组件用于显示一组与当前上下文相关的预定义的问题或建议。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+prompts/basic
+
+:::
+
+### 不可用状态
+
+:::demo 要将 prompt 标记为禁用,请向 prompt 添加 `disabled` 属性。
+
+prompts/disabled
+
+:::
+
+### 纵向展示
+
+:::demo 使用 `vertical` 属性,控制 Prompts 展示方向。
+
+prompts/flex-vertical
+
+:::
+
+### 可换行
+
+:::demo 使用 `wrap` 属性,控制 Prompts 超出区域长度时是否可以换行。
+
+prompts/flex-wrap
+
+:::
+
+### 响应式宽度
+
+:::demo 配合 `wrap` 与 `styles` 固定宽度展示。
+
+prompts/flex-wrap-fixed
+
+:::
+
+### 嵌套组合
+
+:::demo 嵌套组合。
+
+prompts/nest
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### PromptsProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| classNames | 自定义样式类名,用于各个提示项的不同部分 | Record<SemanticType, string> | - | - |
+| items | 包含多个提示项的列表 | PromptProps[] | - | - |
+| prefixCls | 样式类名的前缀 | string | - | - |
+| rootClassName | 根节点的样式类名 | string | - | - |
+| styles | 自定义样式,用于各个提示项的不同部分 | Record<SemanticType, CSSProperties> | - | - |
+| title | 显示在提示列表顶部的标题 | VNode \| string | - | - |
+| vertical | 设置为 `true` 时, 提示列表将垂直排列 | boolean | `false` | - |
+| wrap | 设置为 `true` 时, 提示列表将自动换行 | boolean | `false` | - |
+| onItemClick | 提示项被点击时的回调函数 | (info: { data: PromptProps }) => void | - | - |
+
+#### SemanticType
+
+```typescript | pure
+type SemanticType = 'list' | 'item' | 'content' | 'title' | 'subList' | 'subItem';
+```
+
+### Prompts Slots
+
+| 插槽名 | 说明 |
+| --- | --- |
+| title | 显示在提示列表顶部的标题 |
+
+### PromptProps
+
+| 属性        | 说明                         | 类型            | 默认值  | 版本 |
+| ----------- | ---------------------------- | --------------- | ------- | ---- |
+| children    | 嵌套的子提示项               | PromptProps[]   | -       | -    |
+| description | 提示描述提供额外的信息       | VNode \| string | -       | -    |
+| disabled    | 设置为 `true` 时禁用点击事件 | boolean         | `false` | -    |
+| icon        | 提示图标显示在提示项的左侧   | VNode | -       | -    |
+| key         | 唯一标识用于区分每个提示项   | string          | -       | -    |
+| label       | 提示标签显示提示的主要内容   | VNode \| string | -       | -    |
+
+## Semantic DOM
+
+<vp-semantic component="Prompts"></vp-semantic>
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="Prompts"></ComponentTokenTable> -->

+ 159 - 0
docs/component/sender.md

@@ -0,0 +1,159 @@
+
+# Sender 输入框
+
+用于聊天的输入框组件。
+
+## 何时使用
+
+需要构建一个对话场景下的输入框
+
+## 代码演示
+
+### 基本用法
+
+:::demo 基础用法,受控进行状态管理。自定义触发器。
+
+sender/basic
+
+:::
+
+### 提交用法
+
+:::demo 通过 `submitType` 控制换行与提交模式。
+
+sender/submitType
+
+:::
+
+### 语音输入
+
+:::demo 语音输入,需要用户同意麦克风权限。
+
+sender/speech
+
+:::
+
+### 自定义语音输入
+
+:::demo 自定义语音逻辑,从而实现调用三方库的语音识别功能。
+
+sender/speechCustom
+
+:::
+
+### 自定义按钮
+
+:::demo 通过 `actions` 属性,可以自定义发送按钮的行为。
+
+sender/actions
+
+:::
+
+### 展开面板
+
+:::demo 使用 `header` 自定义文件上传示例。
+
+sender/header
+
+:::
+
+### 引用
+
+:::demo 使用 `header` 做引用效果。
+
+sender/headerFixed
+
+:::
+
+### 调整样式
+
+:::demo 通过 `actions` 属性,调整默认样式。
+
+sender/sendStyle
+
+:::
+
+### 黏贴图片
+
+:::demo 配合 Attachments 进行黏贴文件上传。
+
+sender/pasteImage
+
+:::
+
+### 聚焦
+
+:::demo 使用 `ref` 选项控制聚焦。
+
+sender/focus
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### SenderProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| actions | 自定义按钮 | VNode \| (oriNode, info: { components }) => VNode | - | - |
+| allowSpeech | 是否允许语音输入 | boolean \| SpeechConfig | false | - |
+| classNames | 样式类名 | [见下](#semantic-dom) | - | - |
+| components | 自定义组件 | Record<'input', ComponentType> | - | - |
+| defaultValue | 输入框默认值 | string | - | - |
+| disabled | 是否禁用 | boolean | false | - |
+| loading | 是否加载中 | boolean | false | - |
+| header | 头部面板 | VNode | - | - |
+| prefix | 前缀内容 | VNode | - | - |
+| readOnly | 是否让输入框只读 | boolean | false | - |
+| rootClassName | 根元素样式类 | string | - | - |
+| styles | 语义化定义样式 | [见下](#semantic-dom) | - | - |
+| submitType | 提交模式 | SubmitType | `enter` \| `shiftEnter` | - |
+| value(v-model) | 输入框值 | string | - | - |
+| onSubmit | 点击发送按钮的回调 | (message: string) => void | - | - |
+| onChange | 输入框值改变的回调 | (value: string, event?: FormEvent \| ChangeEvent ) => void | - | - |
+| onCancel | 点击取消按钮的回调 | () => void | - | - |
+
+```typescript | pure
+type SpeechConfig = {
+  // 当设置 `recording` 时,内置的语音输入功能将会被禁用。
+  // 交由开发者实现三方语音输入的功能。
+  recording?: boolean;
+  onRecordingChange?: (recording: boolean) => void;
+};
+```
+
+### Sender Slots
+
+| 插槽名 | 说明 |
+| --- | --- |
+| header | 头部面板 |
+| prefix | 前缀内容 |
+
+#### Sender Ref
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| nativeElement | 外层容器 | `HTMLDivElement` | - | - |
+| focus | 获取焦点 | (option?: { preventScroll?: boolean, cursor?: 'start' \| 'end' \| 'all' }) | - | - |
+| blur | 取消焦点 | () => void | - | - |
+
+### Sender.Header
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| children | 面板内容 | VNode | - | - |
+| closable | 是否可关闭 | boolean | true | - |
+| forceRender | 强制渲染,在初始化便需要 ref 内部元素时使用 | boolean | false | - |
+| open | 是否展开 | boolean | - | - |
+| title | 标题 | VNode | - | - |
+| onOpenChange | 展开状态改变的回调 | (open: boolean) => void | - | - |
+
+## Semantic DOM
+
+<vp-semantic component="Sender"></vp-semantic>
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="Sender"></ComponentTokenTable> -->

+ 78 - 0
docs/component/suggestion.md

@@ -0,0 +1,78 @@
+
+# Suggestion 快捷指令
+
+用于给予用户快捷提示的组件。
+
+## 何时使用
+
+需要构建一个对话场景下的输入框。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法,受控进行状态管理。自定义触发器。
+
+suggestion/basic
+
+:::
+
+### 整行宽度
+
+:::demo 通过 `block` 改为整行展示,`extra` 可用于配置额外信息。
+
+suggestion/block
+
+:::
+
+### 自定义
+
+:::demo 根据输入动态展示建议项的多标签示例。
+
+suggestion/trigger
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### SuggestionsProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| block | 是否整行宽度 | boolean | false | - |
+| children | 自定义输入框 | ({ onTrigger, onKeyDown }) => VNode | - | - |
+| items | 建议项列表 | SuggestionItem[] \| ((info: T) => SuggestionItem[]) | - | - |
+| open | 受控打开面板 | boolean | - | - |
+| rootClassName | 根元素样式类名 | string | - | - |
+| onSelect | 选中建议项回调 | (value: string) => void | - | - |
+| onOpenChange | 面板打开状态变化回调 | (open: boolean) => void | - | - |
+
+#### onTrigger
+
+```typescript | pure
+type onTrigger<T> = (info: T | false) => void;
+```
+
+Suggestion 接受泛型以自定义传递给 `items` renderProps 的参数类型,当传递 `false` 时,则关闭建议面板。
+
+### Suggestions Slots
+
+| 插槽名 | 说明 | 类型 |
+| --- | --- | --- |
+| default | 用于自定义输入框 | \{ onTrigger, onKeyDown \} |
+
+### SuggestionItem
+
+| 属性     | 说明           | 类型             | 默认值 | 版本 |
+| -------- | -------------- | ---------------- | ------ | ---- |
+| children | 子项目         | SuggestionItem[] | -      | -    |
+| extra    | 建议项额外内容 | VNode \| string   | -      | -    |
+| icon     | 建议项图标     | VNode        | -      | -    |
+| label    | 建议项显示内容 | VNode \| string   | -      | -    |
+| value    | 建议项值       | string           | -      | -    |
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="Suggestion"></ComponentTokenTable> -->

+ 103 - 0
docs/component/thought-chain.md

@@ -0,0 +1,103 @@
+
+# ThoughtChain 思维链
+
+思维链组件用于可视化和追踪 Agent 对 Actions 和 Tools 的调用链。
+
+## 何时使用
+
+* 调试和跟踪复杂 Agent System 中的调用链
+* 类似的链式场景中使用
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+thought-chain/basic
+
+:::
+
+### 尺寸
+
+:::demo ThoughtChain 有大、中、小三种尺寸。默认尺寸为中 - `middle`。
+
+thought-chain/size
+
+:::
+
+### 节点状态
+
+:::demo 思维链节点支持配置 `status` 属性来明显的表明当前节点的执行状态
+
+thought-chain/status
+
+:::
+
+### 可折叠的
+
+:::demo 配置 `collapsible` 可开启对思维链节点内容区域的折叠功能
+
+thought-chain/collapsible
+
+:::
+
+### 客制化
+
+:::demo `items` 属性支持灵活的客制化配置,详情参考 `ThoughtChainItem` 定义
+
+thought-chain/customization
+
+:::
+
+### 嵌套使用
+
+:::demo ThoughtChain 组件支持嵌套使用
+
+thought-chain/nested
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### ThoughtChainProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| collapsible | 是否可折叠 | boolean \| CollapsibleOptions | - | - |
+| classNames | 语义化结构的类名 | Record<'item' \| 'itemHeader' \| 'itemContent' \| 'itemFooter', string> | - | - |
+| items | 思维节点集合 | ThoughtChainItem[] | - | - |
+| prefixCls | 自定义前缀 | string | - | - |
+| rootClassName | 自定义根类名 | string | - | - |
+| size | 尺寸 | 'large' \| 'middle' \| 'small' | 'middle' | - |
+| styles | 语义化结构的样式 | Record<'item' \| 'itemHeader' \| 'itemContent' \| 'itemFooter', CSSProperties> | - | - |
+
+### ThoughtChainItem
+
+| 属性        | 说明               | 类型                              | 默认值 | 版本 |
+| ----------- | ------------------ | --------------------------------- | ------ | ---- |
+| content     | 思维节点内容       | VNode \| string         | -      | -    |
+| description | 思维节点描述       | VNode \| string         | -      | -    |
+| extra       | 思维节点额外内容   | VNode \| string          | -      | -    |
+| footer      | 思维节点脚注       | VNode \| string          | -      | -    |
+| icon        | 思维节点图标       | VNode                   | -      | -    |
+| key         | 思维节点唯一标识符 | string                            | -      | -    |
+| status      | 思维节点状态       | 'pending' \| 'success' \| 'error' | -      | -    |
+| title       | 思维节点标题       | VNode \| string          | -      | -    |
+
+### CollapsibleOptions
+
+| 属性         | 说明                   | 类型                             | 默认值 | 版本 |
+| ------------ | ---------------------- | -------------------------------- | ------ | ---- |
+| expandedKeys | 当前展开的节点         | string[]                         | -      | -    |
+| onExpand     | 展开节点变化的回调函数 | (expandedKeys: string[]) => void | -      | -    |
+
+## Semantic DOM
+
+<!-- <code src="./demo/_semantic.tsx" simplify="true"></code> -->
+
+## 主题变量(Design Token)
+
+<!-- <ComponentTokenTable component="ThoughtChain"></ComponentTokenTable> -->

+ 79 - 0
docs/component/use-x-agent.md

@@ -0,0 +1,79 @@
+
+# useXAgent 模型调度
+
+用于模型调度的 Agent 钩子。
+
+## 何时使用
+
+与后端模型进行交互,提供抽象数据流。
+
+## 代码演示
+
+### 预设请求
+
+:::demo 我们将 XRequest 作为预设请求,仅需配置 `baseURL`、`model` 即可
+
+use-x-agent/preset
+
+:::
+
+### 自定义请求
+
+:::demo 通过定制能力,返回多个推荐内容。
+
+use-x-agent/custom
+
+:::
+
+## API
+
+```tsx | pure
+type useXAgent<AgentMessage> = (
+  config: XAgentConfigPreset | XAgentConfigCustom<AgentMessage>,
+) => [Agent];
+```
+
+### XAgentConfigPreset
+
+使用预设协议进行请求,尚未实现协议。
+
+<!-- todo: add dangerouslyApiKey desc link -->
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| baseURL | 请求服务端地址 | string | - |  |
+| key | 请求秘钥 | string | - |  |
+| model | 协议模型 | string | - |  |
+| dangerouslyApiKey | **注意: 🔥 `dangerouslyApiKey` 存在安全风险,对此有详细的[说明](/#)。** | string | - | - |
+
+### XAgentConfigCustom
+
+自定义请求协议。
+
+| 属性    | 说明                         | 类型      | 默认值 | 版本 |
+| ------- | ---------------------------- | --------- | ------ | ---- |
+| request | 配置自定义请求,支持流式更新 | RequestFn |        |      |
+
+#### RequestFn
+
+```tsx | pure
+interface RequestFnInfo<Message> extends Partial<XAgentConfigPreset>, AnyObject {
+  messages?: Message[];
+  message?: Message;
+}
+
+export type RequestFn<Message> = (
+  info: RequestFnInfo<Message>,
+  callbacks: {
+    onUpdate: (message: Message) => void;
+    onSuccess: (message: Message) => void;
+    onError: (error: Error) => void;
+  },
+) => void;
+```
+
+### Agent
+
+| 属性         | 说明                        | 类型          | 版本 |
+| ------------ | --------------------------- | ------------- | ---- |
+| request      | 调用 `useXAgent` 配置的请求 | RequestFn     |      |
+| isRequesting | 是否正在请求                | () => boolean |      |

+ 69 - 0
docs/component/use-x-chat.md

@@ -0,0 +1,69 @@
+
+# useXChat 数据管理
+
+配合 Agent hook 进行对话数据管理。
+
+## 何时使用
+
+通过 Agent 进行会话数据管理,并产出供页面渲染使用的数据。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+use-x-chat/basic
+
+:::
+
+### 流式输出
+
+:::demo 使用流式输出更新内容。
+
+use-x-chat/stream
+
+:::
+
+### 打断输出
+
+:::demo 打断正在流式输出的内容。
+
+use-x-chat/stream-cancel
+
+:::
+
+### 多项建议
+
+:::demo 通过定制能力,返回多个推荐内容。
+
+use-x-chat/suggestions
+
+:::
+
+## API
+
+```tsx | pure
+type useXChat<AgentMessage, ParsedMessage = AgentMessage> = (
+  config: XChatConfig<AgentMessage, ParsedMessage>,
+) => XChatConfigReturnType;
+```
+
+### XChatConfig
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| agent | 通过 `useXAgent` 生成的 `agent`,当使用 `onRequest` 方法时, `agent` 参数是必需的。 | XAgent | - |  |
+| defaultMessages | 默认展示信息 | { status, message }[] | - |  |
+| parser | 将 AgentMessage 转换成消费使用的 ParsedMessage,不设置时则直接消费 AgentMessage。支持将一条 AgentMessage 转换成多条 ParsedMessage | (message: AgentMessage) => BubbleMessage \| BubbleMessage[] | - |  |
+| requestFallback | 请求失败的兜底信息,不提供则不会展示 | AgentMessage \| () => AgentMessage | - |  |
+| requestPlaceholder | 请求中的占位信息,不提供则不会展示 | AgentMessage \| () => AgentMessage | - |  |
+
+### XChatConfigReturnType
+
+| 属性 | 说明 | 类型 | 版本 |
+| --- | --- | --- | --- |
+| messages | 当前管理的内容 | AgentMessages[] |  |
+| parsedMessages | 经过 `parser` 转译过的内容 | ParsedMessages[] |  |
+| onRequest | 添加一条 Message,并且触发请求 | (message) => void |  |
+| setMessages | 直接修改 messages,不会触发请求 | (messages: { message, status }[]) => void |  |

+ 67 - 0
docs/component/welcome.md

@@ -0,0 +1,67 @@
+
+# Welcome 欢迎
+
+清晰传达给用户可实现的意图范围和预期功能。
+
+## 何时使用
+
+使用合适的欢迎推荐组件,可以有效降低用户学习成本,让用户快速了解并顺利开始。
+
+## 代码演示
+
+### 基本
+
+:::demo 基础用法。
+
+welcome/basic
+
+:::
+
+### 变体
+
+:::demo 通过 `variant` 属性设置样式变体。
+
+welcome/variant
+
+:::
+
+### 背景定制
+
+:::demo 自定义部分样式。
+
+welcome/background
+
+:::
+
+## API
+
+<!-- 通用属性参考:[通用属性](/docs/react/common-props) -->
+
+### WelcomeProps
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| classNames | 自定义样式类名,用于各个提示项的不同部分。 | Record<'icon' \| 'title' \| 'description' \| 'extra', string> | - | - |
+| description | 显示在提示列表中的描述。 | VNode \| string | - | - |
+| extra | 显示在提示列表末尾的额外操作。 | VNode | - | - |
+| icon | 显示在提示列表前侧的图标。 | VNode | - | - |
+| rootClassName | 根节点的样式类名。 | string | - | - |
+| styles | 自定义样式,用于各个提示项的不同部分。 | Record<'icon' \| 'title' \| 'description' \| 'extra', CSSProperties> | - | - |
+| title | 显示在提示列表顶部的标题。 | VNode \| string | - | - |
+| variant | 变体类型。 | 'filled' \| 'borderless' | 'filled' | - |
+
+### Welcome Slots
+
+| 插槽名 | 说明 |
+| --- | --- |
+| title | 显示在提示列表顶部的标题 |
+| description | 显示在提示列表中的描述 |
+| icon | 显示在提示列表前侧的图标 |
+| extra | 显示在提示列表末尾的额外操作 |
+
+
+## Semantic DOM
+
+<vp-semantic component="Welcome"></vp-semantic>
+
+## 主题变量(Design Token)

+ 47 - 0
docs/component/x-provider.md

@@ -0,0 +1,47 @@
+# XProvider 全局化配置
+
+为组件提供统一的全局化配置。
+
+## 使用说明
+
+`XProvider` 继承了 `antdv` 的 `ConfigProvider`,且为 `ant-design-x-vue` 中的组件提供全局化配置。
+
+如果您已经使用 `antdv` 的 `ConfigProvider`,请对您的代码做如下变更:
+
+```diff
+- import { ConfigProvider } from 'ant-design-vue';
++ import { XProvider } from 'ant-design-x-vue';
+
+  const App = () => (
+-   <ConfigProvider>
++   <XProvider>
+      <YourApp />
+-   </ConfigProvider>
++   </XProvider>
+  );
+```
+
+## 代码演示
+
+### 使用
+
+:::demo 如何使用
+
+x-provider/use
+
+:::
+
+## API
+
+`XProvider` 完全继承 `antdv` 的 `ConfigProvider`, 属性参考:[Antdv ConfigProvider](https://www.antdv.com/components/config-provider-cn#api)
+
+### 组件配置
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| bubble | 气泡组件的全局配置 | 'classNames' \| 'styles' \| 'className' \| 'style' | - |  |
+| conversations | 会话组件的全局配置 | 'classNames' \| 'styles' \| 'className' \| 'style' | - |  |
+| prompts | 提示集组件的全局配置 | 'classNames' \| 'styles' \| 'className' \| 'style' | - |  |
+| sender | 输入框组件的全局配置 | 'classNames' \| 'styles' \| 'className' \| 'style' | - |  |
+| suggestion | 建议组件的全局配置 | 'className' \| 'style' | - |  |
+| thoughtChain | 思维链组件的全局配置 | 'classNames' \| 'styles' \| 'className' \| 'style' | - |  |

+ 64 - 0
docs/component/x-request.md

@@ -0,0 +1,64 @@
+
+# XRequest 请求
+
+## 何时使用
+
+* 向符合 OpenAI 标准的 LLM 发起请求。
+
+## 代码演示
+
+### 基础
+
+:::demo 该示例说明如何使用 XRequest 对符合 OpenAI 标准的 LLM 发起 fetch 请求 ,请拷贝代码且在 DEV 环境用实际的值替换 BASE_URL, PATH, MODEL, API_KEY 来使用
+
+x-request/basic
+
+:::
+
+### 自定义转换器
+
+:::demo 为 `XRequest` 配置自定义的 `transformStream`, 示例中使用 `application/x-ndjson` 数据演示
+
+x-request/custom-transformer
+
+:::
+
+## API
+
+### XRequestOptions
+
+<!-- todo: add dangerouslyApiKey decs link  -->
+
+| 属性 | 描述 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| baseURL | API 请求的基础 URL | string | - | - |
+| model | 模型名称,例如 'gpt-3.5-turbo' | string | - | - |
+| dangerouslyApiKey | **注意: 🔥 `dangerouslyApiKey` 存在安全风险,对此有详细的[说明](/#)。** | string | - | - |
+| fetch | 可选的自定义 fetch 函数,用于发起请求 | fetch | - | - |
+
+### XRequestFunction
+
+```ts
+type XRequestFunction<Input = Record<PropertyKey, any>, Output = Record<string, string>> = (
+  params: XRequestParams & Input,
+  callbacks: XRequestCallbacks<Output>,
+  transformStream?: XStreamOptions<Output>['transformStream'],
+) => Promise<void>;
+```
+
+#### XRequestParams
+
+| 属性     | 描述                                   | 类型                       | 默认值 | 版本 |
+| -------- | -------------------------------------- | -------------------------- | ------ | ---- |
+| model    | 生成响应时使用的模型。                 | string                     | -      | -    |
+| messages | 消息对象数组,每个对象包含角色和内容。 | Record<PropertyKey, any>[] | -      | -    |
+| stream   | 指示是否使用流式响应。                 | boolean                    | false  | -    |
+
+#### XRequestCallbacks
+
+| 属性 | 描述 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| onSuccess | 成功时的回调。 | `(messages: Output[]) => void` | - | - |
+| onError | 错误处理的回调。 | `(error: Error) => void` | - | - |
+| onUpdate | 消息更新的回调。 | `(message: Output) => void` | - | - |
+| transformStream | 可选的转换函数,用于处理流数据 | `XStreamOptions<Output>['transformStream']` | - | - |

+ 59 - 0
docs/component/x-stream.md

@@ -0,0 +1,59 @@
+
+# XStream 流
+
+转换可读数据流。
+
+## 何时使用
+
+* 将 SSE 协议的 `ReadableStream` 转换为 `Record`
+* 将任何协议的 `ReadableStream` 解码并读取
+
+## 使用说明
+
+常见的 `ReadableStream` 实例,如 `await fetch(...).body` 使用示例:
+
+```js
+import { XStream } from '@ant-design/x';
+
+async function request() {
+  const response = await fetch();
+  // .....
+
+  for await (const chunk of XStream({
+    readableStream: response.body,
+  })) {
+    console.log(chunk);
+  }
+}
+```
+
+## 代码演示
+
+### 默认协议 - SSE
+
+> SSE - https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
+
+:::demo XStream 默认的 `transformStream` 是用于 SSE 协议的流转换器。`readableStream` 接收一个 `new ReadableStream(...)` 实例,常见的如 `await fetch(...).body`
+
+x-stream/default-protocol
+
+:::
+
+### 自定义协议
+
+> 在本示例中,我们将演示如何解析 SIP 协议, 该协议常用于 P2P 音视频会话协商。
+
+:::demo 传入 `transformStream` 流转换器,该参数需接收一个 `new TransformStream(...)` 实例。
+
+x-stream/custom-protocol
+
+:::
+
+## API
+
+### XStreamOptions
+
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| readableStream | ReadableStream 实例 | ReadableStream<'Uint8Array'> | - | - |
+| transformStream | 自定义的 transformStream 用于转换流的处理 | TransformStream<string, T> | sseTransformStream | - |

+ 184 - 0
docs/development/introduce.md

@@ -0,0 +1,184 @@
+
+# Ant Design X of Vue
+
+`ant-design-x-vue` 是遵循 Ant Design 设计体系的一个 Vue UI 库,是 `@ant-design/x` 的 Vue 实现,用于构建由 AI 驱动的界面,一键接入智能对话组件与 API 服务。
+
+<div style="display: flex; align-items: center; gap: 20px;">
+  <img width="150" src="/images/x-logo.svg"/>
+  <span style="color: rgb(170, 170, 170); font-size: 30px; use-select: none;">+</span>
+  <img width="160" src="/images/vue.svg"/>
+</div>
+
+---
+
+## ✨ 特性
+
+- 🌈 基于 RICH 交互范式,提供卓越的 AI 交互体验
+- 🧩 覆盖绝大部分 AI 对话场景,助力快速构建个性化 AI 交互页面
+- ⚡ 轻松对接符合 OpenAI 标准的模型推理服务
+- 🔄 提供好用的数据流管理功能,让开发更高效
+- 📦 提供多种模板,快速启动 LUI 应用开发
+- 🛡 采用 TypeScript 开发,提供完整类型支持,提升开发体验与可靠性
+- 🎨 支持细粒度的样式调整,满足各种场景的个性化需求
+
+## 安装
+
+### 使用 npm 或 yarn 或 pnpm 或 bun 安装
+
+::: code-group
+
+```sh [npm]
+$ npm install ant-design-x-vue --save
+```
+
+```sh [pnpm]
+$ pnpm install ant-design-x-vue --save
+```
+
+```sh [yarn]
+$ yarn add ant-design-x-vue
+```
+
+```sh [bun]
+$ bun add ant-design-x-vue
+```
+
+:::
+
+### 浏览器引入
+
+在浏览器中使用 `script` 和 `link` 标签直接引入文件,并使用全局变量 `antdx`。
+
+<span style="display: flex;gap: 4px;align-items: center;">你可以通过 [![](https://data.jsdelivr.com/v1/package/npm/ant-design-x-vue/badge)](https://www.jsdelivr.com/package/npm/ant-design-x-vue) 进行下载。</span>
+
+### 自动导入
+
+在项目中自动导入组件,需要使用 `unplugin-vue-components` 插件,如果未安装,你可以通过以下命令安装:
+
+::: code-group
+
+```sh [npm]
+$ npm install unplugin-vue-components -D
+```
+
+```sh [pnpm]
+$ pnpm install unplugin-vue-components -D
+```
+
+```sh [yarn]
+$ yarn add unplugin-vue-components -D
+```
+
+```sh [bun]
+$ bun add unplugin-vue-components -d
+```
+
+:::
+
+在 `Vite` 或 `Webpack` 的配置文件中,添加以下依赖和插件:
+
+::: code-group
+
+```js [vite.config.js]
+// vite.config.js
+import vue from '@vitejs/plugin-vue';
+import { defineConfig } from 'vite';
+
+// add the following dependencies
+import components from 'unplugin-vue-components/vite';
+import { AntDesignXVueResolver } from 'ant-design-x-vue/resolver';
+
+export default defineConfig({
+  // ...
+  plugins: [
+    // add the following plugin
+    components({
+      resolvers: [AntDesignXVueResolver()]
+    })
+  ]
+});
+```
+
+```js [webpack.config.js]
+// webpack.config.js
+
+// add the following dependencies
+const Components = require('unplugin-vue-components/webpack');
+const { AntDesignXVueResolver } = require('ant-design-x-vue/resolver');
+
+module.exports = {
+  configureWebpack: {
+    plugins: [
+      // add the following plugin
+      Components.default({
+        resolvers: [AntDesignXVueResolver()]
+      })
+    ]
+  }
+};
+```
+
+:::
+
+更多打包工具的示例,可以参考 [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components#installation)
+
+在项目中,以 `AX` 开头的组件,将被自动解析为 `ant-design-x-vue` 组件并导入到文件中,你可以直接在模板中使用它们:
+
+```html
+<script setup>
+// auto import equals to
+// import { Bubble as AXBubble } from 'ant-design-x-vue';
+</script>
+
+<template>
+  <AXBubble content="Hello AI" />
+</template>
+```
+
+
+
+## 🧩 原子组件
+
+我们基于 RICH 交互范式,在不同的交互阶段提供了大量的原子组件,帮助你灵活搭建你的 AI 对话应用:
+
+- 通用: `Bubble` - 消息气泡、`Conversations` - 会话管理
+- 唤醒: `Welcome` - 欢迎、`Prompts` - 提示集
+- 表达: `Sender` - 发送框、`Attachment` - 附件、`Suggestion` - 快捷指令
+- 确认: `ThoughtChain` - 思维链
+
+下面是使用原子组件搭建一个最简单的对话框的代码示例:
+
+```vue
+<script setup lang="ts">
+import {
+  // 消息气泡
+  Bubble,
+  // 发送框
+  Sender,
+} from 'ant-design-x-vue';
+
+defineOptions({ name: 'SampleChat' });
+
+const messages = [
+  {
+    content: 'Hello, Ant Design X Vue!',
+    role: 'user',
+  },
+];
+</script>
+
+<template>
+  <Bubble.List :items="messages" />
+  <Sender />
+</template>
+```
+
+这里有一个基本的示例项目,你可以参考:[Example project](https://github.com/wzc520pyfm/ant-design-x-vue/tree/main/play)
+
+::: warning
+
+文档中 tsx 风格的代码示例采用 [`defineRender`](https://vue-macros.dev/zh-CN/macros/define-render.html) 编写,要运行它们,你还需要集成 [`Vue Macros`](https://vue-macros.dev/zh-CN/guide/bundler-integration.html).
+
+Vue模板风格的示例代码在逐步更新中,你可以在左上角将 **风格偏好** 切换至 `setup` 预览。
+
+:::

+ 58 - 0
docs/examples-setup/attachments/basic.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { CloudUploadOutlined, LinkOutlined } from '@ant-design/icons-vue';
+import { message, Button, Flex, Switch, type UploadFile } from 'ant-design-vue';
+import { Attachments, Sender } from 'ant-design-x-vue';
+import { computed, h, ref } from 'vue';
+
+defineOptions({ name: 'AXAttachmentBasic' });
+
+
+const fullScreenDrop = ref(false);
+const customContent = ref(true);
+const divRef = ref<HTMLDivElement>();
+const [messageApi, contextHolder] = message.useMessage();
+const handleChange = ({ file }: { file: UploadFile }) => {
+  messageApi.info(`Mock upload: ${file.name}`);
+};
+
+const getDropContainer = () => (fullScreenDrop.value ? document.body : divRef.value);
+
+const attachmentsNode = computed(() => h(Attachments, {
+  beforeUpload: () => false,
+  onChange: handleChange,
+  placeholder: {
+    icon: h(CloudUploadOutlined),
+    title: 'Drag & Drop files here',
+    description: 'Support file type: image, video, audio, document, etc.',
+  },
+  children: customContent.value && h(Button, { type: 'text', icon: h(LinkOutlined) }),
+  getDropContainer,
+}));
+
+</script>
+
+<template>
+  <contextHolder />
+  <div ref="divRef">
+    <Flex
+      vertical
+      gap="middle"
+      align="flex-start"
+    >
+      <Sender
+        :prefix="attachmentsNode"
+      />
+      <Switch
+        v-model:checked="fullScreenDrop"
+        checked-children="Full screen drop"
+        un-checked-children="Full screen drop"
+      />
+
+      <Switch
+        v-model:checked="customContent"
+        checked-children="use default content"
+        un-checked-children="custom content"
+      />
+    </Flex>
+  </div>
+</template>

+ 75 - 0
docs/examples-setup/attachments/files.vue

@@ -0,0 +1,75 @@
+<script setup lang="ts">
+import { Flex } from 'ant-design-vue';
+import { Attachments } from 'ant-design-x-vue';
+
+defineOptions({ name: 'AXAttachmentFiles' });
+
+const filesList = [
+  {
+    uid: '1',
+    name: 'excel-file.xlsx',
+    size: 111111,
+  },
+  {
+    uid: '2',
+    name: 'word-file.docx',
+    size: 222222,
+  },
+  {
+    uid: '3',
+    name: 'image-file.png',
+    size: 333333,
+  },
+  {
+    uid: '4',
+    name: 'pdf-file.pdf',
+    size: 444444,
+  },
+  {
+    uid: '5',
+    name: 'ppt-file.pptx',
+    size: 555555,
+  },
+  {
+    uid: '6',
+    name: 'video-file.mp4',
+    size: 666666,
+  },
+  {
+    uid: '7',
+    name: 'audio-file.mp3',
+    size: 777777,
+  },
+  {
+    uid: '8',
+    name: 'zip-file.zip',
+    size: 888888,
+  },
+  {
+    uid: '9',
+    name: 'markdown-file.md',
+    size: 999999,
+    description: 'Custom description here',
+  },
+  {
+    uid: '10',
+    name: 'image-file.png',
+    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+    url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+    size: 123456,
+  },
+];
+</script>
+
+<template>
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Attachments.FileCard
+      v-for="(file, index) in filesList"
+      :key="index"
+      :item="file"
+    />
+  </Flex>
+</template>

+ 72 - 0
docs/examples-setup/attachments/overflow.vue

@@ -0,0 +1,72 @@
+<script setup lang="ts">
+import { CloudUploadOutlined } from '@ant-design/icons-vue';
+import { Flex, Segmented, Switch } from 'ant-design-vue';
+import { Attachments, type AttachmentsProps } from 'ant-design-x-vue';
+import { ref, h, computed } from 'vue';
+
+defineOptions({ name: 'AXAttachmentOverflow' });
+
+const presetFiles: AttachmentsProps['items'] = Array.from({ length: 30 }).map((_, index) => ({
+  uid: String(index),
+  name: `file-${index}.jpg`,
+  status: 'done',
+  thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+}));
+
+const overflow = ref<AttachmentsProps['overflow']>('wrap');
+const items = ref<AttachmentsProps['items']>(presetFiles);
+const disabled = ref(false);
+
+// 使用计算属性来处理 items.length !== 0
+const hasItems = computed({
+  get: () => items.value.length !== 0,
+  set: (value) => {
+    items.value = value ? presetFiles : [];
+  }
+});
+</script>
+
+<template>
+  <Flex
+    vertical
+    :gap="'middle'"
+  >
+    <Flex
+      :gap="'middle'"
+      :align="'center'"
+    >
+      <Segmented
+        v-model:value="overflow"
+        :options="[
+          { value: 'wrap', label: 'Wrap' },
+          { value: 'scrollX', label: 'Scroll X' },
+          { value: 'scrollY', label: 'Scroll Y' },
+        ]"
+        style="margin-inline-end: auto"
+      />
+      <Switch
+        v-model:checked="hasItems"
+        checked-children="Data"
+        un-checked-children="Data"
+      />
+      <Switch
+        v-model:checked="disabled"
+        checked-children="Disabled"
+        un-checked-children="Disabled"
+      />
+    </Flex>
+    <Attachments
+      :overflow="overflow"
+      :items="items"
+      :before-upload="() => false"
+      :placeholder="{
+        icon: h(CloudUploadOutlined),
+        title: 'Click or Drop files here',
+        description: 'Support file type: image, video, audio, document, etc.',
+      }"
+      :disabled="disabled"
+      @change="(info) => items = info.fileList"
+    />
+  </Flex>
+</template>

+ 169 - 0
docs/examples-setup/attachments/placeholder.vue

@@ -0,0 +1,169 @@
+<script setup lang="ts">
+import { CloudUploadOutlined } from '@ant-design/icons-vue';
+import { Button, Flex, Result, theme, Typography } from 'ant-design-vue';
+import { Attachments, type AttachmentsProps } from 'ant-design-x-vue';
+import { computed, type CSSProperties, ref } from 'vue';
+
+defineOptions({ name: 'AXAttachmentPlaceHolderSetup' });
+
+const presetFiles: AttachmentsProps['items'] = [
+  {
+    uid: '1',
+    name: 'uploading file.xlsx',
+    status: 'uploading',
+    url: 'http://www.baidu.com/xxx.png',
+    percent: 93,
+  },
+  {
+    uid: '2',
+    name: 'uploaded file.docx',
+    status: 'done',
+    size: 123456,
+    description: 'Customize description',
+    url: 'http://www.baidu.com/yyy.png',
+  },
+  {
+    uid: '3',
+    name: 'upload error with long text file name.zip',
+    status: 'error',
+    response: 'Server Error 500', // custom error message to show
+    url: 'http://www.baidu.com/zzz.png',
+  },
+  {
+    uid: '4',
+    name: 'image uploading preview.png',
+    status: 'uploading',
+    percent: 33,
+    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+    url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  },
+  {
+    uid: '5',
+    name: 'image done preview.png',
+    status: 'done',
+    size: 123456,
+    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+    url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  },
+  {
+    uid: '6',
+    name: 'image error preview.png',
+    status: 'error',
+    response: 'Server Error 500', // custom error message to show
+    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+    url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  },
+];
+
+const { token } = theme.useToken();
+const items = ref<AttachmentsProps['items']>([]);
+
+const sharedBorderStyle = computed<CSSProperties>(() => ({
+  borderRadius: token.value.borderRadius,
+  overflow: 'hidden',
+  background: token.value.colorBgContainer,
+}));
+
+const sharedAttachmentProps = computed<AttachmentsProps>(() => ({
+  beforeUpload: () => false,
+  items: items.value,
+  onChange: ({ fileList }) => {
+    console.log('onChange:', fileList);
+    items.value = fileList;
+  },
+}));
+
+const fillFiles = () => {
+  items.value = presetFiles;
+};
+
+const resetFiles = () => {
+  items.value = [];
+};
+</script>
+<template>
+  <Flex
+    vertical
+    gap="middle"
+    :style="{
+      padding: token.padding,
+      background: token.colorBgContainerDisabled,
+    }"
+  >
+    <div :style="sharedBorderStyle">
+      <Attachments
+        v-bind="sharedAttachmentProps"
+      >
+        <template #placeholder="{ type }">
+          <Flex
+            v-if="type === 'inline'"
+            align="center"
+            justify="center"
+            vertical
+            gap="2"
+          >
+            <Typography.Text style="font-size: 30px; line-height: 1;">
+              <CloudUploadOutlined />
+            </Typography.Text>
+            <Typography.Title
+              :level="5"
+              style="margin: 0; font-size: 14px; line-height: 1.5;"
+            >
+              Click or Drop files here
+            </Typography.Title>
+            <Typography.Text type="secondary">
+              Support file type: image, video, audio, document, etc.
+            </Typography.Text>
+          </Flex>
+          <Typography.Text v-if="type === 'drop'">
+            Drop file here
+          </Typography.Text>
+        </template>
+      </Attachments>
+    </div>
+
+    <div :style="sharedBorderStyle">
+      <Attachments
+        v-bind="sharedAttachmentProps"
+      >
+        <template #placeholder="{ type }">
+          <Result
+            v-if="type === 'inline'"
+            title="Custom Placeholder Node"
+            :style="{ padding: 0 }"
+          >
+            <template #icon>
+              <CloudUploadOutlined />
+            </template>
+            <template #extra>
+              <Button type="primary">
+                Do Upload
+              </Button>
+            </template>
+          </Result>
+          <Typography.Text v-if="type === 'drop'">
+            Drop file here
+          </Typography.Text>
+        </template>
+      </Attachments>
+    </div>
+
+    <Flex gap="middle">
+      <Button
+        :style="{ flex: '1 1 50%' }"
+        :disabled="!!items.length"
+        type="primary"
+        @click="fillFiles"
+      >
+        Fill Files
+      </Button>
+      <Button
+        :style="{ flex: '1 1 50%' }"
+        :disabled="!items.length"
+        @click="resetFiles"
+      >
+        Reset Files
+      </Button>
+    </Flex>
+  </Flex>
+</template>

+ 70 - 0
docs/examples-setup/attachments/with-sender.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { CloudUploadOutlined, LinkOutlined } from '@ant-design/icons-vue';
+import { App, Button, Flex, Badge } from 'ant-design-vue';
+import { Attachments, Sender } from 'ant-design-x-vue';
+import { computed, ref, h } from 'vue';
+
+defineOptions({ name: 'AXAttachmentWithSender' });
+
+const open = ref(true);
+const items = ref([]);
+const text = ref('');
+
+const senderRef = ref<InstanceType<typeof Sender>>(null);
+
+const senderHeader = computed(() => 
+  h(Sender.Header, {
+    title: "Attachments",
+    styles: {
+      content: {
+        padding: 0,
+      },
+    },
+    open: open.value,
+    onOpenChange: (v: boolean) => open.value = v,
+    forceRender: true
+  }, {
+    default: () => h(Attachments, {
+      beforeUpload: () => false,
+      items: items.value,
+      onChange: ({ fileList }: { fileList: any[] }) => items.value = fileList,
+      placeholder: (type: string) => type === 'drop'
+        ? { title: 'Drop file here' }
+        : {
+            icon: h(CloudUploadOutlined),
+            title: 'Upload files',
+            description: 'Click or drag files to this area to upload',
+          },
+      getDropContainer: () => senderRef.value?.nativeElement
+    })
+  })
+);
+
+const badgeNode = computed(() => 
+  h(Badge, { dot: items.value.length > 0 && !open.value }, {
+    default: () => h(Button, {
+      onClick: () => open.value = !open.value,
+      icon: h(LinkOutlined)
+    })
+  })
+);
+</script>
+
+<template>
+  <Flex
+    style="min-height: 250px"
+    align="flex-end"
+  >
+    <Sender
+      ref="senderRef"
+      :header="senderHeader"
+      :prefix="badgeNode"
+      :value="text"
+      @change="v => text.value = v"
+      @submit="() => {
+        items.value = [];
+        text.value = '';
+      }"
+    />
+  </Flex>
+</template>

+ 59 - 0
docs/examples-setup/bubble/avatar-and-placement.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { Bubble } from 'ant-design-x-vue';
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Flex, Avatar } from 'ant-design-vue';
+import type { CSSProperties } from 'vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXBubbleAvatarAndPlacementSetup' });
+
+const fooAvatar: CSSProperties = {
+  color: '#f56a00',
+  backgroundColor: '#fde3cf',
+};
+
+const barAvatar: CSSProperties = {
+  color: '#fff',
+  backgroundColor: '#87d068',
+};
+
+const hideAvatar: CSSProperties = {
+  visibility: 'hidden',
+};
+</script>
+
+<template>
+  <Flex
+    gap="middle"
+    vertical
+  >
+    <Bubble
+      placement="start"
+      content="Good morning, how are you?"
+      :avatar="{ icon: h(UserOutlined), style: fooAvatar }"
+    />
+    <Bubble
+      placement="start"
+      content="What a beautiful day!"
+      :styles="{ avatar: hideAvatar }"
+      :avatar="{}"
+    />
+    <Bubble
+      placement="end"
+      content="Hi, good morning, I'm fine!"
+    >
+      <template #avatar>
+        <Avatar
+          :icon="h(UserOutlined)"
+          :style="barAvatar"
+        />
+      </template>
+    </Bubble>
+    <Bubble
+      placement="end"
+      content="Thank you!"
+      :styles="{ avatar: hideAvatar }"
+      :avatar="{}"
+    />
+  </Flex>
+</template>

+ 9 - 0
docs/examples-setup/bubble/basic.vue

@@ -0,0 +1,9 @@
+<script setup lang="ts">
+import { Bubble } from 'ant-design-x-vue';
+
+defineOptions({ name: 'AXBubbleBasicSetup' });
+</script>
+
+<template>
+  <Bubble content="hello world  !" />
+</template>

+ 75 - 0
docs/examples-setup/bubble/bubble-custom.vue

@@ -0,0 +1,75 @@
+<script setup lang="ts">
+import {
+  FrownOutlined,
+  SmileOutlined,
+  SyncOutlined,
+  UserOutlined,
+} from '@ant-design/icons-vue';
+import { BubbleList } from 'ant-design-x-vue';
+import { Button, Flex, Space, Spin } from 'ant-design-vue';
+import type { BubbleListProps } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXBubbleBubbleCustomSetup' });
+
+const roles: BubbleListProps['roles'] = {
+  ai: {
+    placement: 'start',
+    avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
+    typing: { step: 5, interval: 20 },
+    style: {
+      maxWidth: 600,
+      marginInlineEnd: 44,
+    },
+    styles: {
+      footer: {
+        width: '100%',
+      },
+    },
+    loadingRender: () =>
+      h(Space, null, [h(Spin, { size: 'small' }), 'Custom loading...']),
+  },
+  user: {
+    placement: 'end',
+    avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },
+  },
+};
+
+// const listRef = useTemplateRef<InstanceType<typeof BubbleList>>(null);
+const listRef = ref<InstanceType<typeof BubbleList>>(null);
+</script>
+
+<template>
+  <BubbleList
+    ref="listRef"
+    :style="{ maxHeight: 300 }"
+    :roles="roles"
+    :items="[
+      {
+        key: 'welcome',
+        role: 'ai',
+        content: 'Mock welcome content. '.repeat(10),
+        footer: h(Flex, null, [
+          h(Button, {
+            size: 'small',
+            type: 'text',
+            icon: h(SyncOutlined),
+            style: { marginInlineEnd: 'auto' },
+          }),
+          h(Button, { size: 'small', type: 'text', icon: h(SmileOutlined) }),
+          h(Button, { size: 'small', type: 'text', icon: h(FrownOutlined) }),
+        ]),
+      },
+      {
+        key: 'ask',
+        role: 'user',
+        content: 'Mock user content.',
+      },
+      {
+        key: 'ai',
+        role: 'ai',
+        loading: true,
+      },
+    ]"
+  />
+</template>

+ 57 - 0
docs/examples-setup/bubble/gpt-vis.vue

@@ -0,0 +1,57 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+import { Bubble } from 'ant-design-x-vue';
+import type { BubbleProps } from 'ant-design-x-vue';
+// import { GPTVis } from '@antv/gpt-vis';
+import { Button, Flex } from 'ant-design-vue';
+
+defineOptions({ name: 'AXBubbleGptVisSetup' });
+
+const text = `
+**GPT-Vis**, Components for GPTs, generative AI, and LLM projects. Not only UI Components. [more...](https://github.com/antvis/GPT-Vis) \n\n
+
+Here’s a visualization of Haidilao's food delivery revenue from 2013 to 2022. You can see a steady increase over the years, with notable *growth* particularly in recent years.
+
+\`\`\`vis-chart
+{
+  "type": "line",
+  "data": [{"time":2013,"value":59.3},{"time":2014,"value":64.4},{"time":2015,"value":68.9},{"time":2016,"value":74.4},{"time":2017,"value":82.7},{"time":2018,"value":91.9},{"time":2019,"value":99.1},{"time":2020,"value":101.6},{"time":2021,"value":114.4},{"time":2022,"value":121}],
+  "axisXTitle": "year",
+  "axisYTitle": "sale"
+}
+\`\`\`
+`;
+
+const RenderMarkdown: BubbleProps['messageRender'] = (content) => {
+  // The @antv/gpt-vis is only support React
+  // return <GPTVis>{{content}}</GPTVis>;
+  return content;
+};
+
+const rerenderKey = ref(0);
+</script>
+
+<template>
+  <Flex vertical gap="small" :key="rerenderKey">
+    <Button
+      :style="{ alignSelf: 'flex-end' }"
+      @click="
+        () => {
+          rerenderKey = rerenderKey + 1;
+        }
+      "
+    >
+      Re-Render
+    </Button>
+
+    <Bubble
+      :typing="{ step: 20, interval: 150 }"
+      :content="text"
+      :messageRender="RenderMarkdown"
+      :avatar="{
+        src: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*2Q5LRJ3LFPUAAAAAAAAAAAAADmJ7AQ/fmt.webp',
+      }"
+      variant="outlined"
+    />
+  </Flex>
+</template>

+ 29 - 0
docs/examples-setup/bubble/header-and-footer.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import {
+  CopyOutlined,
+  SyncOutlined,
+  UserOutlined,
+} from '@ant-design/icons-vue';
+import { Bubble } from 'ant-design-x-vue';
+import { Button, Space, theme, Avatar } from 'ant-design-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXBubbleHeaderAndFooterSetup' });
+
+const { token } = theme.useToken();
+</script>
+
+<template>
+  <Bubble
+    content="Hello, welcome to use Ant Design X! Just ask if you have any questions."
+    :avatar="{ icon: h(UserOutlined) }"
+    header="Ant Design X"
+  >
+    <template #footer>
+      <Space :size="token.paddingXXS">
+        <Button type="text" size="small" :icon="h(SyncOutlined)"></Button>
+        <Button type="text" size="small" :icon="h(CopyOutlined)" />
+      </Space>
+    </template>
+  </Bubble>
+</template>

+ 107 - 0
docs/examples-setup/bubble/list-custom.vue

@@ -0,0 +1,107 @@
+<script setup lang="ts">
+import {
+  CoffeeOutlined,
+  FireOutlined,
+  SmileOutlined,
+  UserOutlined,
+} from '@ant-design/icons-vue';
+import { Attachments, BubbleList, Prompts } from 'ant-design-x-vue';
+import { Flex, Typography } from 'ant-design-vue';
+import type { BubbleListProps } from 'ant-design-x-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXBubbleListCustomSetup' });
+
+const roles: BubbleListProps['roles'] = {
+  ai: {
+    placement: 'start',
+    typing: true,
+    avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
+  },
+  suggestion: {
+    placement: 'start',
+    avatar: { icon: h(UserOutlined), style: { visibility: 'hidden' } },
+    variant: 'borderless',
+    messageRender: (items) =>
+      h(Prompts, { vertical: true, items: items as any }),
+  },
+  file: {
+    placement: 'start',
+    avatar: { icon: h(UserOutlined), style: { visibility: 'hidden' } },
+    variant: 'borderless',
+    messageRender: (items: any) =>
+      h(
+        Flex,
+        { vertical: true, gap: 'middle' },
+        items.map((item: any) =>
+          h(Attachments.FileCard, { key: item.uid, item: item }),
+        ),
+      ),
+  },
+};
+</script>
+
+<template>
+  <BubbleList
+    :roles="roles"
+    :items="[
+      // Normal
+      {
+        key: 0,
+        role: 'ai',
+        content: 'Normal message',
+      },
+
+      // ReactNode
+      {
+        key: 1,
+        role: 'ai',
+        content: h(Typography.Text, { type: 'danger' }, 'ReactNode message'),
+      },
+
+      // Role: suggestion
+      {
+        key: 2,
+        role: 'suggestion',
+        content: [
+          {
+            key: '6',
+            icon: h(CoffeeOutlined, { style: { color: '#964B00' } }),
+            description: 'How to rest effectively after long hours of work?',
+          },
+          {
+            key: '7',
+            icon: h(SmileOutlined, { style: { color: '#FAAD14' } }),
+            description:
+              'What are the secrets to maintaining a positive mindset?',
+          },
+          {
+            key: '8',
+            icon: h(FireOutlined, { style: { color: '#FF4D4F' } }),
+            description: 'How to stay calm under immense pressure?',
+          },
+        ],
+      },
+      // Role: file
+      {
+        key: 3,
+        role: 'file',
+        content: [
+          {
+            uid: '1',
+            name: 'excel-file.xlsx',
+            size: 111111,
+            description: 'Checking the data',
+          },
+          {
+            uid: '2',
+            name: 'word-file.docx',
+            size: 222222,
+            status: 'uploading',
+            percent: 23,
+          },
+        ],
+      },
+    ]"
+  />
+</template>

+ 116 - 0
docs/examples-setup/bubble/list.vue

@@ -0,0 +1,116 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { BubbleList } from 'ant-design-x-vue';
+import { Button, Flex, Switch } from 'ant-design-vue';
+import type { BubbleListProps, BubbleProps } from 'ant-design-x-vue';
+import type { SwitchProps } from 'ant-design-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXBubbleListSetup' });
+
+const rolesAsObject: BubbleListProps['roles'] = {
+  ai: {
+    placement: 'start',
+    avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
+    typing: { step: 5, interval: 20 },
+    style: {
+      maxWidth: '600px',
+    },
+  },
+  user: {
+    placement: 'end',
+    avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },
+  },
+};
+
+const rolesAsFunction = (bubbleData: BubbleProps, index: number) => {
+  const RenderIndex: BubbleProps['messageRender'] = (content) =>
+    h(Flex, null, [h('text', null, `#${index}: ${content}`)]);
+  switch (bubbleData.role) {
+    case 'ai':
+      return {
+        placement: 'start' as const,
+        avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
+        typing: { step: 5, interval: 20 },
+        style: {
+          maxWidth: 600,
+        },
+        messageRender: RenderIndex,
+      };
+    case 'user':
+      return {
+        placement: 'end' as const,
+        avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },
+        messageRender: RenderIndex,
+      };
+    default:
+      return { messageRender: RenderIndex };
+  }
+};
+
+const count = ref<number>(3);
+const useRolesAsFunction = ref(false);
+
+// const listRef = useTemplateRef<InstanceType<typeof BubbleList>>(null);
+const listRef = ref<InstanceType<typeof BubbleList>>(null);
+
+const handleChange = (checked: SwitchProps['checked']) => {
+  useRolesAsFunction.value = checked as boolean;
+};
+</script>
+
+<template>
+  <Flex vertical gap="small">
+    <Flex gap="large" align="center">
+      Use roles as:
+      <Switch
+        :checked="useRolesAsFunction"
+        @change="handleChange"
+        checkedChildren="Function"
+        unCheckedChildren="Object"
+      />
+    </Flex>
+
+    <Flex gap="small" :style="{ alignSelf: 'flex-end' }">
+      <Button
+        @click="
+          () => {
+            count++;
+          }
+        "
+      >
+        Add Bubble
+      </Button>
+
+      <Button
+        @click="
+          () => {
+            listRef?.scrollTo({ key: 0, block: 'nearest' });
+          }
+        "
+      >
+        Scroll To First
+      </Button>
+    </Flex>
+
+    <BubbleList
+      ref="listRef"
+      :style="{ maxHeight: '300px' }"
+      :roles="useRolesAsFunction ? rolesAsFunction : rolesAsObject"
+      :items="
+        Array.from({ length: count }).map((_, i) => {
+          const isAI = !!(i % 2);
+          const content = isAI
+            ? 'Mock AI content. '.repeat(20)
+            : 'Mock user content.';
+
+          return {
+            key: i,
+            role: isAI ? 'ai' : 'user',
+            content,
+          };
+        })
+      "
+    />
+  </Flex>
+</template>

+ 24 - 0
docs/examples-setup/bubble/loading.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Flex, Switch, Avatar } from 'ant-design-vue';
+import { Bubble } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXBubbleLoadingSetup' });
+
+const loading = ref(true);
+</script>
+
+<template>
+  <Flex gap="large" vertical>
+    <Bubble
+      :loading="loading"
+      content="hello world !"
+      :avatar="{ icon: h(UserOutlined) }"
+    />
+    <Flex gap="large" wrap="wrap">
+      Loading state:
+      <Switch v-model:checked="loading" />
+    </Flex>
+  </Flex>
+</template>

+ 45 - 0
docs/examples-setup/bubble/markdown.vue

@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Bubble } from 'ant-design-x-vue';
+import type { BubbleProps } from 'ant-design-x-vue';
+import { Typography } from 'ant-design-vue';
+import markdownit from 'markdown-it';
+import { onWatcherCleanup, ref, watchEffect, h } from 'vue';
+
+defineOptions({ name: 'AXBubbleMarkdownSetup' });
+
+const md = markdownit({ html: true, breaks: true });
+
+const text = `
+> Render as markdown content to show rich text!
+
+Link: [Ant Design X](https://x.ant.design)
+`.trim();
+
+const renderMarkdown: BubbleProps['messageRender'] = (content) =>
+  h(Typography, null, {
+    default: () => h('div', { innerHTML: md.render(content) }),
+  });
+
+const renderKey = ref(0);
+
+watchEffect(() => {
+  const id = setTimeout(() => {
+    renderKey.value = renderKey.value + 1;
+  }, text.length * 100 + 2000);
+  onWatcherCleanup(() => {
+    clearTimeout(id);
+  });
+});
+</script>
+
+<template>
+  <div :style="{ height: 100 }" :key="renderKey">
+    <Bubble
+      :typing="true"
+      :content="text"
+      :messageRender="renderMarkdown"
+      :avatar="{ icon: h(UserOutlined) }"
+    />
+  </div>
+</template>

+ 26 - 0
docs/examples-setup/bubble/shape.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { Bubble, type BubbleProps } from 'ant-design-x-vue';
+import { Flex } from 'ant-design-vue';
+
+defineOptions({ name: 'AXBubbleShapeSetup' });
+
+const sharedLongTextProps: BubbleProps = {
+  placement: 'end',
+  content:
+    'This is a long text message to show the multiline view of the bubble component. '.repeat(
+      3,
+    ),
+  styles: { content: { maxWidth: 500 } },
+};
+</script>
+
+<template>
+  <Flex gap="middle" vertical>
+    <Bubble content="shape: default" />
+    <Bubble v-bind="sharedLongTextProps" />
+    <Bubble content="shape: round" shape="round" />
+    <Bubble v-bind="sharedLongTextProps" shape="round" />
+    <Bubble content="shape: corner" shape="corner" />
+    <Bubble v-bind="sharedLongTextProps" shape="corner" />
+  </Flex>
+</template>

+ 34 - 0
docs/examples-setup/bubble/typing.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Bubble } from 'ant-design-x-vue';
+import { Button, Flex } from 'ant-design-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXBubbleTypingSetup' });
+
+const text = ref('Ant Design X love you! ');
+
+const repeat = ref(1);
+</script>
+
+<template>
+  <Flex vertical gap="small">
+    <Bubble
+      :content="text.repeat(repeat)"
+      :typing="{ step: 2, interval: 50 }"
+      :avatar="{ icon: h(UserOutlined) }"
+    />
+    <Bubble
+      :content="text.repeat(repeat)"
+      :typing="{ step: 2, interval: 50, suffix: '💗' }"
+      :avatar="{ icon: h(UserOutlined) }"
+    />
+
+    <Button
+      :style="{ alignSelf: 'flex-end' }"
+      @click="repeat = repeat < 5 ? repeat + 1 : 1"
+    >
+      Repeat {{ repeat }} Times
+    </Button>
+  </Flex>
+</template>

+ 62 - 0
docs/examples-setup/bubble/variant.vue

@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import {
+  CoffeeOutlined,
+  FireOutlined,
+  SmileOutlined,
+  UserOutlined,
+} from '@ant-design/icons-vue';
+import { Bubble, Prompts, type PromptsProps } from 'ant-design-x-vue';
+import { Flex } from 'ant-design-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXBubbleVariantSetup' });
+
+const items: PromptsProps['items'] = [
+  {
+    key: '6',
+    icon: h(CoffeeOutlined, { style: { color: '#964B00' } }),
+    description: 'How to rest effectively after long hours of work?',
+  },
+  {
+    key: '7',
+    icon: h(SmileOutlined, { style: { color: '#FAAD14' } }),
+    description: 'What are the secrets to maintaining a positive mindset?',
+  },
+  {
+    key: '8',
+    icon: h(FireOutlined, { style: { color: '#FF4D4F' } }),
+    description: 'How to stay calm under immense pressure?',
+  },
+];
+</script>
+
+<template>
+  <Flex vertical gap="middle">
+    <Bubble
+      variant="filled"
+      :avatar="{ icon: h(UserOutlined) }"
+      content="variant: filled"
+    />
+    <Bubble
+      variant="outlined"
+      :avatar="{ icon: h(UserOutlined) }"
+      content="variant: outlined"
+    />
+    <Bubble
+      variant="shadow"
+      :avatar="{ icon: h(UserOutlined) }"
+      content="variant: shadow"
+    />
+    <Bubble
+      variant="borderless"
+      :avatar="{ icon: h(UserOutlined) }"
+      :content="
+        h(Prompts, {
+          title: 'variant: borderless to customize',
+          items,
+          vertical: true,
+        })
+      "
+    />
+  </Flex>
+</template>

+ 6 - 0
docs/examples-setup/conversations/basic.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXConversationsBasicSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/conversations/controlled-mode.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXConversationsControlledModeSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/conversations/group-sort.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXConversationsGroupSortSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/conversations/group.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXConversationsGroupSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/conversations/with-menu.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXConversationsWithMenuSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/playground/independent.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'PlaygroundIndependentSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 61 - 0
docs/examples-setup/prompts/basic.vue

@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import {
+  BulbOutlined,
+  InfoCircleOutlined,
+  RocketOutlined,
+  SmileOutlined,
+  WarningOutlined,
+} from '@ant-design/icons-vue';
+import { message as messageAnt } from 'ant-design-vue';
+import { Prompts, type PromptsProps } from 'ant-design-x-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXPromptsBasicSetup' });
+
+const [message, contextHolder] = messageAnt.useMessage();
+
+const items: PromptsProps['items'] = [
+  {
+    key: '1',
+    icon: h(BulbOutlined, { style: { color: '#FFD700' } }),
+    label: 'Ignite Your Creativity',
+    description: 'Got any sparks for a new project?',
+  },
+  {
+    key: '2',
+    icon: h(InfoCircleOutlined, { style: { color: '#1890FF' } }),
+    label: 'Uncover Background Info',
+    description: 'Help me understand the background of this topic.',
+  },
+  {
+    key: '3',
+    icon: h(RocketOutlined, { style: { color: '#722ED1' } }),
+    label: 'Efficiency Boost Battle',
+    description: 'How can I work faster and better?',
+  },
+  {
+    key: '4',
+    icon: h(SmileOutlined, { style: { color: '#52C41A' } }),
+    label: 'Tell me a Joke',
+    description: 'Why do not ants get sick? Because they have tiny ant-bodies!',
+  },
+  {
+    key: '5',
+    icon: h(WarningOutlined, { style: { color: '#FF4D4F' } }),
+    label: 'Common Issue Solutions',
+    description: 'How to solve common issues? Share some tips!',
+  },
+];
+
+</script>
+
+<template>
+  <context-holder />
+  <Prompts
+    title="✨ Inspirational Sparks and Marvelous Tips"
+    :items="items"
+    :on-item-click="(info) => {
+      message.success(`You clicked a prompt: ${info.data.label}`);
+    }"
+  />
+</template>

+ 6 - 0
docs/examples-setup/prompts/disabled.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXPromptsDisabledSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/prompts/flex-vertical.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXPromptsFlexVerticalSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/prompts/flex-wrap-fixed.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXPromptsFlexWrapFixedSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/prompts/flex-wrap.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXPromptsFlexWrapSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/prompts/nest.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXPromptsNestSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/actions.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderActionsSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 55 - 0
docs/examples-setup/sender/basic.vue

@@ -0,0 +1,55 @@
+<script setup lang="ts">
+import { message, Flex } from 'ant-design-vue';
+import { Sender } from 'ant-design-x-vue';
+import { onWatcherCleanup, ref, watch } from 'vue';
+
+defineOptions({ name: 'AXSenderBasicSetup' });
+
+const [messageApi, contextHolder] = message.useMessage();
+
+const value = ref('Hello? this is X!');
+const loading = ref<boolean>(false);
+
+// Mock send message
+watch(loading, () => {
+  if (loading.value) {
+    const timer = setTimeout(() => {
+      loading.value = false;
+      messageApi.success('Send message successfully!');
+    }, 3000);
+    onWatcherCleanup(() => {
+      clearTimeout(timer);
+    })
+  }
+});
+</script>
+<template>
+  <contextHolder />
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Sender
+      v-model:value="value"
+      :loading="loading"
+      @submit="() => {
+        value = '';
+        loading = true
+        messageApi.info('Send message!');
+      }"
+      @cancel="() => {
+        loading = false
+        messageApi.error('Cancel sending!');
+      }"
+    />
+    <Sender
+      value="Force as loading"
+      loading
+      read-only
+    />
+    <Sender
+      disabled
+      value="Set to disabled"
+    />
+  </Flex>
+</template>

+ 6 - 0
docs/examples-setup/sender/focus.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderFocusSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 83 - 0
docs/examples-setup/sender/header.vue

@@ -0,0 +1,83 @@
+<script setup lang="ts">
+import { CloudUploadOutlined, LinkOutlined } from '@ant-design/icons-vue';
+import { Button, Flex, theme, Typography, message as messageAnt } from 'ant-design-vue';
+import { Sender } from 'ant-design-x-vue';
+import { ref } from 'vue';
+
+defineOptions({ name: 'AXSenderHeaderSetup' });
+
+const { token } = theme.useToken();
+const [message, contextHolder] = messageAnt.useMessage();
+
+const open = ref(false);
+
+const openChange = (v: boolean) => {
+  open.value = v;
+}
+
+const triggerOpen = () => {
+  open.value = !open.value;
+}
+
+const senderSubmit = () => {
+  message.success('Send message successfully!');
+}
+
+const selectFileClick = () => {
+  message.info('Mock select file');
+}
+
+</script>
+
+<template>
+  <context-holder />
+  <Flex
+    style="height: 350px;"
+    align="end"
+  >
+    <Sender
+      placeholder="← Click to open"
+      @submit="senderSubmit"
+    >
+      <template #header>
+        <Sender.Header
+          title="Upload Sample"
+          :open="open"
+          @open-change="openChange"
+        >
+          <Flex
+            vertical
+            align="center"
+            gap="small"
+            :style="{ marginBlock: token.paddingLG }"
+          >
+            <CloudUploadOutlined style="font-size: 4em" />
+            <Typography.Title
+              :level="5"
+              style="margin: 0"
+            >
+              Drag file here(just demo)
+            </Typography.Title>
+            <Typography.Text type="secondary">
+              Support pdf, doc, xlsx, ppt, txt, image file types
+            </Typography.Text>
+            <Button @click="selectFileClick">
+              Select File
+            </Button>
+          </Flex>
+        </Sender.Header>
+      </template>
+
+      <template #prefix>
+        <Button
+          type="text"
+          @click="triggerOpen"
+        >
+          <template #icon>
+            <LinkOutlined />
+          </template>
+        </Button>
+      </template>
+    </Sender>
+  </Flex>
+</template>

+ 6 - 0
docs/examples-setup/sender/headerFixed.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderHeaderFixedSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/pasteImage.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderPasteImageSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/sendStyle.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderSendStyleSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/speech.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderSpeechSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/speechCustom.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderSpeechCustomSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 6 - 0
docs/examples-setup/sender/submitType.vue

@@ -0,0 +1,6 @@
+<script setup lang="ts">
+defineOptions({ name: 'AXSenderSubmitTypeSetup' });
+</script>
+<template>
+  <div>Needs to be supplemented.</div>
+</template>

+ 61 - 0
docs/examples-setup/suggestion/basic.vue

@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { RedditOutlined } from '@ant-design/icons-vue';
+import { Sender, Suggestion, type SuggestionProps } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXSuggestionBasicSetup' });
+
+type SuggestionItems = Exclude<SuggestionProps['items'], () => void>;
+
+const suggestions: SuggestionItems = [
+  { label: 'Write a report', value: 'report' },
+  { label: 'Draw a picture', value: 'draw' },
+  {
+    label: 'Check some knowledge',
+    value: 'knowledge',
+    icon: h(RedditOutlined, { displayName: '' }),
+    children: [
+      {
+        label: 'About React',
+        value: 'react',
+      },
+      {
+        label: 'About Ant Design',
+        value: 'antd',
+      },
+    ],
+  },
+];
+
+const value = ref('')
+
+const updateValue = (v: string) => {
+  value.value = v
+}
+
+
+</script>
+<template>
+  <Suggestion
+    :items="suggestions"
+    @select="(itemVal) => {
+      updateValue(`[${itemVal}]:`);
+    }"
+  >
+    <template #default="{ onTrigger, onKeyDown }">
+      <Sender
+        :value="value"
+        @change="(nextVal) => {
+          if (nextVal === '/') {
+            onTrigger();
+          } else if (!nextVal) {
+            onTrigger(false);
+          }
+          updateValue(nextVal);
+        }"
+        @keydown="onKeyDown"
+        placeholder="输入 / 获取建议"
+      />
+    </template>
+  </Suggestion>
+</template>

+ 45 - 0
docs/examples-setup/suggestion/block.vue

@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import { Sender, Suggestion, type SuggestionProps } from 'ant-design-x-vue';
+import { ref } from 'vue';
+
+defineOptions({ name: 'AXSuggestionBlockSetup' });
+
+type SuggestionItems = Exclude<SuggestionProps['items'], () => void>;
+
+const suggestions: SuggestionItems = [
+  { label: 'Write a report', value: 'report' },
+  { label: 'Draw a picture', value: 'draw' },
+  {
+    label: 'Check some knowledge',
+    value: 'knowledge',
+    extra: 'Extra Info',
+  },
+];
+
+const value = ref('');
+</script>
+<template>
+  <Suggestion
+    :items="suggestions"
+    block
+    @select="(itemVal) => {
+      value = `[${itemVal}]:`;
+    }"
+  >
+    <template #default="{ onTrigger, onKeyDown }">
+      <Sender
+        :value="value"
+        :on-change="(nextVal) => {
+          if (nextVal === '/') {
+            onTrigger();
+          } else if (!nextVal) {
+            onTrigger(false);
+          }
+          value = nextVal;
+        }"
+        :on-keydown="onKeyDown"
+        placeholder="输入 / 获取建议"
+      />
+    </template>
+  </Suggestion>
+</template>

+ 46 - 0
docs/examples-setup/suggestion/trigger.vue

@@ -0,0 +1,46 @@
+<script setup lang="ts">
+import { Select } from 'ant-design-vue';
+import { Suggestion } from 'ant-design-x-vue';
+import { ref } from 'vue';
+
+defineOptions({ name: 'AXSuggestionTriggerSetup' });
+
+const uuid = ref(0);
+const tags = ref<string[]>([]);
+const value = ref('');
+</script>
+<template>
+  <Suggestion
+    :items="(info) => [{ label: `Trigger by '${info}'`, value: String(info) }]"
+    @select="(info) => {
+      uuid += 1;
+      tags = [...tags, `Cell_${uuid}`];
+      value = value.replace(info, '');
+    }"
+  >
+    <template #default="{ onTrigger, onKeyDown }">
+      <Select
+        :value="tags"
+        :style="{ width: '100%' }"
+        mode="tags"
+        :open="false"
+        :search-value="value"
+        placeholder="可任意输入 / 与 # 多次获取建议"
+        @change="(nextTags) => {
+          if ((nextTags as string[]).length < tags.length) {
+            tags = nextTags as string[];
+          }
+        }"
+        @search="(nextVal) => {
+          value = nextVal;
+        }"
+        @keydown="(e) => {
+          if (e.key === '/' || e.key === '#') {
+            onTrigger(e.key);
+          }
+          onKeyDown(e);
+        }"
+      />
+    </template>
+  </Suggestion>
+</template>

+ 36 - 0
docs/examples-setup/thought-chain/basic.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { MoreOutlined } from '@ant-design/icons-vue';
+import { Button, Card } from 'ant-design-vue';
+import { ThoughtChain, type ThoughtChainProps } from 'ant-design-x-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainBasicSetup' });
+
+const items: ThoughtChainProps['items'] = [
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+];
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <ThoughtChain :items="items" />
+  </Card>
+</template>

+ 47 - 0
docs/examples-setup/thought-chain/collapsible.vue

@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { Card, Typography } from 'ant-design-vue';
+import { ThoughtChain, type ThoughtChainProps } from 'ant-design-x-vue';
+import { cloneVNode, h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainCollapsibleSetup' });
+
+const { Paragraph, Text } = Typography;
+
+// 更推荐将 mockContent 封装成 SFC
+const mockContent = h(Typography, {}, () => [
+  h(
+    Paragraph,
+    {},
+    () => 'In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.',
+  ),
+  h(Paragraph, {}, () => [
+    'After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to ',
+    h(
+      Text,
+      { strong: true },
+      () => 'uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development',
+    ),
+  ]),
+]);
+
+const items: ThoughtChainProps['items'] = [
+  {
+    title: 'Click me to expand the content',
+    description: 'Collapsible',
+    content: cloneVNode(h(mockContent)),
+  },
+  {
+    title: 'Click me to expand the content',
+    description: 'Collapsible',
+    content: cloneVNode(h(mockContent)),
+  },
+];
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <ThoughtChain
+      :items="items"
+      collapsible
+    />
+  </Card>
+</template>

+ 65 - 0
docs/examples-setup/thought-chain/customization.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import { CheckCircleOutlined, MoreOutlined } from '@ant-design/icons-vue';
+import { Button, Card, Typography } from 'ant-design-vue';
+import {
+  ThoughtChain,
+  type ThoughtChainItem,
+  type ThoughtChainProps,
+} from 'ant-design-x-vue';
+import { cloneVNode, isVNode, h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainCustomizationSetup' });
+
+const { Paragraph, Text } = Typography;
+
+const customizationProps: ThoughtChainItem = {
+  title: 'Thought Chain Item Title',
+  description: 'description',
+  icon: h(CheckCircleOutlined),
+  extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  footer: h(Button, { block: true }, () => 'Thought Chain Item Footer'),
+  content: h(Typography, {}, () => [
+    h(
+      Paragraph,
+      {},
+      () => 'In the process of internal desktop applications development, many different design specs and implementations would be involved, which might cause designers and developers difficulties and duplication and reduce the efficiency of development.',
+    ),
+    h(Paragraph, {}, () => [
+      'After massive project practice and summaries, Ant Design, a design language for background applications, is refined by Ant UED Team, which aims to ',
+      h(
+        Text,
+        { strong: true },
+        () => 'uniform the user interface specs for internal background projects, lower the unnecessary cost of design differences and implementation and liberate the resources of design and front-end development',
+      ),
+    ]),
+  ]),
+};
+
+const cloneCustomizationProps = (op: ThoughtChainItem) => {
+  return Object.fromEntries(
+    Object.entries(op).map(([k, v]) => {
+      return [k, isVNode(v) ? cloneVNode(v) : v];
+    }),
+  );
+};
+
+const items: ThoughtChainProps['items'] = [
+  {
+    ...cloneCustomizationProps(customizationProps),
+    status: 'success',
+  },
+  {
+    ...cloneCustomizationProps(customizationProps),
+    status: 'error',
+  },
+  {
+    ...cloneCustomizationProps(customizationProps),
+    status: 'pending',
+  },
+];
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <ThoughtChain :items="items" />
+  </Card>
+</template>

+ 59 - 0
docs/examples-setup/thought-chain/nested.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { MoreOutlined } from '@ant-design/icons-vue';
+import { Button, Card } from 'ant-design-vue';
+import { ThoughtChain, type ThoughtChainProps } from 'ant-design-x-vue';
+import { h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainNestedSetup' });
+
+const items: ThoughtChainProps['items'] = [
+  {
+    title: '1 - Thought Chain Item',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+    footer: h(Button, {}, () => '1 - Thought Chain Item Footer'),
+    content: h(ThoughtChain, {
+      items: [
+        {
+          title: '1-1 Thought Chain Item',
+          description: 'description',
+          extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+        },
+        {
+          title: '1-2 Thought Chain Item',
+          description: 'description',
+          extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+        },
+      ],
+    }),
+  },
+  {
+    title: '2 - Thought Chain Item',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+    footer: h(Button, {}, () => '2 - Thought Chain Item Footer'),
+    content: h(ThoughtChain, {
+      items: [
+        {
+          title: '2-1 Thought Chain Item',
+          description: 'description',
+          extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+        },
+        {
+          title: '2-2 Thought Chain Item',
+          description: 'description',
+          extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+        },
+      ],
+    }),
+  },
+];
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <ThoughtChain
+      :items="items"
+      collapsible
+    />
+  </Card>
+</template>

+ 64 - 0
docs/examples-setup/thought-chain/size.vue

@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import { MoreOutlined } from '@ant-design/icons-vue';
+import { Button, Card, Radio, type ConfigProviderProps } from 'ant-design-vue';
+import { ThoughtChain, type ThoughtChainProps } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainSizeSetup' });
+
+type SizeType = ConfigProviderProps['componentSize'];
+
+const items: ThoughtChainProps['items'] = [
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+
+  {
+    title: 'Thought Chain Item Title',
+    description: 'description',
+    extra: h(Button, { type: 'text', icon: h(MoreOutlined) }),
+  },
+];
+
+// default size is 'middle'
+const size = ref<SizeType>('middle');
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <Radio.Group
+      :value="size"
+      :style="{ marginBottom: '16px' }"
+      @change="
+        (e) => {
+          size = e.target.value;
+        }
+      "
+    >
+      <Radio.Button value="large">
+        Large
+      </Radio.Button>
+      <Radio.Button value="middle">
+        Default
+      </Radio.Button>
+      <Radio.Button value="small">
+        Small
+      </Radio.Button>
+    </Radio.Group>
+    <ThoughtChain
+      :items="items"
+      :size="size"
+    />
+  </Card>
+</template>

+ 101 - 0
docs/examples-setup/thought-chain/status.vue

@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import {
+  CheckCircleOutlined,
+  InfoCircleOutlined,
+  LoadingOutlined,
+} from '@ant-design/icons-vue';
+import { Button, Card } from 'ant-design-vue';
+import { ThoughtChain, type ThoughtChainItem } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXThoughtChainStatusSetup' });
+
+function getStatusIcon(status: ThoughtChainItem['status']) {
+  switch (status) {
+    case 'success':
+      return h(CheckCircleOutlined);
+    case 'error':
+      return h(InfoCircleOutlined);
+    case 'pending':
+      return h(LoadingOutlined);
+    default:
+      return undefined;
+  }
+}
+
+const mockServerResponseData: ThoughtChainItem[] = [
+  {
+    title: 'Thought Chain Item - 1',
+    status: 'success',
+    description: 'status: success',
+    icon: getStatusIcon('success'),
+  },
+  {
+    title: 'Thought Chain Item - 2',
+    status: 'error',
+    description: 'status: error',
+    icon: getStatusIcon('error'),
+  },
+];
+
+const delay = (ms: number) => {
+  return new Promise<void>((resolve) => {
+    const timer: NodeJS.Timeout = setTimeout(() => {
+      clearTimeout(timer);
+      resolve();
+    }, ms);
+  });
+};
+
+function addChainItem() {
+  mockServerResponseData.push({
+    title: `Thought Chain Item - ${mockServerResponseData.length + 1}`,
+    status: 'pending',
+    icon: getStatusIcon('pending'),
+    description: 'status: pending',
+  });
+}
+
+async function updateChainItem(status: ThoughtChainItem['status']) {
+  await delay(800);
+  mockServerResponseData[mockServerResponseData.length - 1].status = status;
+  mockServerResponseData[mockServerResponseData.length - 1].icon =
+    getStatusIcon(status);
+  mockServerResponseData[
+    mockServerResponseData.length - 1
+  ].description = `status: ${status}`;
+}
+
+const items = ref<ThoughtChainItem[]>(mockServerResponseData);
+const loading = ref(false);
+
+const mockStatusChange = async () => {
+  await updateChainItem('error');
+  items.value = [...mockServerResponseData];
+  await updateChainItem('pending');
+  items.value = [...mockServerResponseData];
+  await updateChainItem('success');
+  items.value = [...mockServerResponseData];
+};
+
+const onClick = async () => {
+  loading.value = true;
+  addChainItem();
+  items.value = [...mockServerResponseData];
+  await mockStatusChange();
+  loading.value = false;
+};
+</script>
+<template>
+  <Card :style="{ width: '500px' }">
+    <Button
+      :loading="loading"
+      :style="{ marginBottom: '16px' }"
+      @click="onClick"
+    >
+      <span v-if="loading">Running</span>
+      <span v-else>Run Next</span>
+    </Button>
+    <ThoughtChain :items="items" />
+  </Card>
+</template>

+ 91 - 0
docs/examples-setup/use-x-agent/custom.vue

@@ -0,0 +1,91 @@
+<script setup lang="ts">
+import { Button, Divider, Form, Input, Typography } from 'ant-design-vue';
+import { useXAgent } from 'ant-design-x-vue';
+import { reactive, ref } from 'vue';
+
+defineOptions({ name: 'AXUseXAgentCustomSetup' });
+
+const lines = ref<string[]>([]);
+
+const modelRef = reactive({
+  question: '',
+});
+
+Form.useForm(modelRef);
+
+const log = (text: string) => {
+  lines.value = [...lines.value, text];
+};
+
+const [ agent ] = useXAgent({
+  request: ({ message }, { onUpdate, onSuccess }) => {
+    let times = 0;
+
+    const id = setInterval(() => {
+      times += 1;
+      onUpdate(`Thinking...(${times}/3)`);
+
+      if (times >= 3) {
+        onSuccess(`It's funny that you ask: ${message}`);
+        clearInterval(id);
+      }
+    }, 500);
+  },
+});
+
+const onFinish = ({ question }: { question: string }) => {
+  log(`[You] Ask: ${question}`);
+  agent.value.request(
+    { message: question },
+    {
+      onUpdate: (message) => {
+        log(`[Agent] Update: ${message}`);
+      },
+      onSuccess: (message) => {
+        log(`[Agent] Answer: ${message}`);
+        modelRef.question = ''
+      },
+      // Current demo not use error
+      onError: () => { },
+    },
+  );
+};
+
+</script>
+<template>
+  <Form
+    :model="modelRef"
+    layout="vertical"
+    auto-complete="off"
+    @finish="onFinish"
+  >
+    <Form.Item
+      label="Question"
+      name="question"
+    >
+      <Input v-model:value="modelRef.question" />
+    </Form.Item>
+    <Form.Item>
+      <Button
+        html-type="submit"
+        type="primary"
+        :loading="agent.isRequesting()"
+      >
+        submit
+      </Button>
+    </Form.Item>
+  </Form>
+
+  <Divider />
+
+  <Typography>
+    <ul :style="{listStyle: 'circle', paddingLeft: 0 }">
+      <li
+        v-for="(line, index) in lines"
+        :key="index"
+      >
+        {{ line }}
+      </li>
+    </ul>
+  </Typography>
+</template>

+ 111 - 0
docs/examples-setup/use-x-agent/preset.vue

@@ -0,0 +1,111 @@
+<script setup lang="ts">
+import { LoadingOutlined, TagsOutlined } from '@ant-design/icons-vue';
+import { Button, Descriptions, Flex } from 'ant-design-vue';
+import {
+  ThoughtChain,
+  useXAgent,
+  type ThoughtChainItem,
+} from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXUseXAgentPresetSetup' });
+
+/**
+ * 🔔 Please replace the BASE_URL, PATH, MODEL, API_KEY with your own values.
+ */
+const BASE_URL = 'https://api.example.com';
+const PATH = '/chat';
+const MODEL = 'gpt-3.5-turbo';
+/** 🔥🔥 Its dangerously! */
+// const API_KEY = '';
+
+interface YourMessageType {
+  role: string;
+  content: string;
+}
+
+const status = ref<ThoughtChainItem['status']>();
+const lines = ref<Record<string, string>[]>([]);
+
+const [agent] = useXAgent<YourMessageType>({
+  baseURL: BASE_URL + PATH,
+  model: MODEL,
+  // dangerouslyApiKey: API_KEY
+});
+
+async function request() {
+  status.value = 'pending';
+
+  agent.value.request(
+    {
+      messages: [{ role: 'user', content: 'hello, who are u?' }],
+      stream: true,
+    },
+    {
+      onSuccess: (messages) => {
+        status.value = 'success';
+        console.log('onSuccess', messages);
+      },
+      onError: (error) => {
+        status.value = 'error';
+        console.error('onError', error);
+      },
+      onUpdate: (msg) => {
+        // @ts-expect-error
+        lines.value = [...lines.value, msg];
+        console.log('onUpdate', msg);
+      },
+    },
+  );
+}
+</script>
+
+<template>
+  <Flex
+    align="start"
+    :gap="16"
+    :style="{ overflow: 'auto' }"
+  >
+    <div>
+      <Button
+        type="primary"
+        :disabled="status === 'pending'"
+        @click="request"
+      >
+        Agent Request
+      </Button>
+    </div>
+    <div>
+      <ThoughtChain
+        :style="{ marginLeft: 16 }"
+        :items="[
+          {
+            title: 'Agent Request Log',
+            status: status,
+            icon: status === 'pending' ? h(LoadingOutlined) : h(TagsOutlined),
+            description:
+              status === 'error' &&
+              agent.config.baseURL === BASE_URL + PATH &&
+              'Please replace the BASE_URL, PATH, MODEL, API_KEY with your own values.',
+            content: h(Descriptions, { column: 1 }, [
+              h(
+                Descriptions.Item,
+                {
+                  label: 'Status',
+                },
+                () => status || '-',
+              ),
+              h(
+                Descriptions.Item,
+                {
+                  label: 'Update Times',
+                },
+                () => lines.length,
+              ),
+            ]),
+          },
+        ]"
+      />
+    </div>
+  </Flex>
+</template>

+ 85 - 0
docs/examples-setup/use-x-chat/basic.vue

@@ -0,0 +1,85 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Flex } from 'ant-design-vue';
+import { Bubble, Sender, useXAgent, useXChat, type BubbleListProps } from 'ant-design-x-vue';
+import { ref } from 'vue';
+
+defineOptions({ name: 'AXUseXChatBasicSetup' });
+
+const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));
+
+const roles: BubbleListProps['roles'] = {
+  ai: {
+    placement: 'start',
+    avatar: { icon: UserOutlined, style: { background: '#fde3cf' } },
+    typing: { step: 5, interval: 20 },
+    style: {
+      maxWidth: '600px',
+    },
+  },
+  local: {
+    placement: 'end',
+    avatar: { icon: UserOutlined, style: { background: '#87d068' } },
+  },
+};
+
+const mockSuccess = ref(false);
+const content = ref('');
+const senderLoading = ref(false);
+
+const setContent = (v: string) => {
+  content.value = v;
+}
+
+// Agent for request
+const [agent] = useXAgent({
+  request: async ({ message }, { onSuccess, onError }) => {
+    senderLoading.value = true;
+    await sleep();
+
+    senderLoading.value = false;
+
+    mockSuccess.value = !mockSuccess.value;
+
+    if (mockSuccess.value) {
+      onSuccess(`Mock success return. You said: ${message}`);
+    }
+
+    onError(new Error('Mock request failed'));
+  },
+});
+
+// Chat messages
+const { onRequest, messages } = useXChat({
+  agent: agent.value,
+  requestPlaceholder: 'Waiting...',
+  requestFallback: 'Mock failed return. Please try again later.',
+});
+
+</script>
+<template>
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Bubble.List
+      :roles="roles"
+      :style="{ maxHeight: '300px' }"
+      :items="messages.map(({ id, message, status }) => ({
+        key: id,
+        loading: status === 'loading',
+        role: status === 'local' ? 'local' : 'ai',
+        content: message,
+      }))"
+    />
+    <Sender
+      :loading="senderLoading"
+      :value="content"
+      :on-change="setContent"
+      :on-submit="(nextContent) => {
+        onRequest(nextContent);
+        setContent('');
+      }"
+    />
+  </Flex>
+</template>

+ 123 - 0
docs/examples-setup/use-x-chat/stream-cancel.vue

@@ -0,0 +1,123 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Flex } from 'ant-design-vue';
+import { Bubble, Sender, useXAgent, useXChat, XStream, type BubbleListProps } from 'ant-design-x-vue';
+import { onWatcherCleanup, ref, watchEffect } from 'vue';
+
+defineOptions({ name: 'AXUseXChatStreamCancelSetup' });
+
+const roles: BubbleListProps['roles'] = {
+  ai: {
+    placement: 'start',
+    avatar: { icon: UserOutlined, style: { background: '#fde3cf' } },
+  },
+  local: {
+    placement: 'end',
+    avatar: { icon: UserOutlined, style: { background: '#87d068' } },
+  },
+};
+
+const contentChunks = [
+  'He',
+  'llo',
+  ', w',
+  'or',
+  'ld!',
+  ' Ant',
+  ' Design',
+  ' X',
+  ' is',
+  ' the',
+  ' best',
+  '!',
+];
+
+function mockReadableStream() {
+  const sseChunks: string[] = [];
+
+  for (let i = 0; i < contentChunks.length; i++) {
+    const sseEventPart = `event: message\ndata: {"id":"${i}","content":"${contentChunks[i]}"}\n\n`;
+    sseChunks.push(sseEventPart);
+  }
+
+  return new ReadableStream({
+    async start(controller) {
+      for (const chunk of sseChunks) {
+        await new Promise((resolve) => setTimeout(resolve, 300));
+        controller.enqueue(new TextEncoder().encode(chunk));
+      }
+      controller.close();
+    },
+  });
+}
+
+const content = ref('');
+const senderLoading = ref(false);
+const abort = ref(() => { });
+
+watchEffect(() => {
+  onWatcherCleanup(() => {
+    abort.value();
+  })
+});
+
+// Agent for request
+const [ agent ] = useXAgent({
+  request: async (_, { onSuccess, onUpdate }) => {
+    senderLoading.value = true;
+    const stream = XStream({
+      readableStream: mockReadableStream(),
+    });
+
+    const reader = stream.getReader();
+    abort.value = () => {
+      reader?.cancel();
+    };
+
+    let current = '';
+    while (reader) {
+      const { value, done } = await reader.read();
+      if (done) {
+        senderLoading.value = false;
+        onSuccess(current);
+        break;
+      }
+      if (!value) continue;
+      const data = JSON.parse(value.data);
+      current += data.content || '';
+      onUpdate(current);
+    }
+  },
+});
+
+// Chat messages
+const { onRequest, messages } = useXChat({
+  agent: agent.value,
+});
+</script>
+<template>
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Bubble.List
+      :roles="roles"
+      :style="{ maxHeight: 300 }"
+      :items="messages.map(({ id, message, status }) => ({
+        key: id,
+        role: status === 'local' ? 'local' : 'ai',
+        content: message,
+      }))"
+    />
+    <Sender
+      :loading="senderLoading"
+      :value="content"
+      :on-change="(v) => content = v"
+      :on-submit="(nextContent) => {
+        onRequest(nextContent);
+        content = '';
+      }"
+      :on-cancel="() => abort()"
+    />
+  </Flex>
+</template>

+ 72 - 0
docs/examples-setup/use-x-chat/stream.vue

@@ -0,0 +1,72 @@
+<script setup lang="ts">
+import { UserOutlined } from '@ant-design/icons-vue';
+import { Flex } from 'ant-design-vue';
+import { Bubble, Sender, useXAgent, useXChat } from 'ant-design-x-vue';
+import { ref } from 'vue';
+
+defineOptions({ name: 'AXUseXChatStreamSetup' });
+
+const roles: (typeof Bubble.List)['roles'] = {
+  ai: {
+    placement: 'start',
+    avatar: { icon: UserOutlined, style: { background: '#fde3cf' } },
+  },
+  local: {
+    placement: 'end',
+    avatar: { icon: UserOutlined, style: { background: '#87d068' } },
+  },
+};
+
+const content = ref('');
+const senderLoading = ref(false);
+
+// Agent for request
+const [ agent ] = useXAgent({
+  request: async ({ message }, { onSuccess, onUpdate }) => {
+    senderLoading.value = true;
+    const fullContent = `Streaming output instead of Bubble typing effect. You typed: ${message}`;
+    let currentContent = '';
+
+    const id = setInterval(() => {
+      currentContent = fullContent.slice(0, currentContent.length + 2);
+      onUpdate(currentContent);
+
+      if (currentContent === fullContent) {
+        senderLoading.value = false;
+        clearInterval(id);
+        onSuccess(fullContent);
+      }
+    }, 100);
+  },
+});
+
+// Chat messages
+const { onRequest, messages } = useXChat({
+  agent: agent.value,
+});
+</script>
+<template>
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Bubble.List
+      :roles="roles"
+      :style="{ maxHeight: 300 }"
+      :items="messages.map(({ id, message, status }) => ({
+        key: id,
+        role: status === 'local' ? 'local' : 'ai',
+        content: message,
+      }))"
+    />
+    <Sender
+      :loading="senderLoading"
+      :value="content"
+      :on-change="(v) => content = v"
+      :on-submit="(nextContent) => {
+        onRequest(nextContent);
+        content = '';
+      }"
+    />
+  </Flex>
+</template>

+ 148 - 0
docs/examples-setup/use-x-chat/suggestions.vue

@@ -0,0 +1,148 @@
+<script setup lang="ts">
+import { SmileOutlined, UserOutlined } from '@ant-design/icons-vue';
+import { Flex } from 'ant-design-vue';
+import { Bubble, Prompts, Sender, useXAgent, useXChat, type BubbleListProps } from 'ant-design-x-vue';
+import { ref, h } from 'vue';
+
+defineOptions({ name: 'AXUseXChatSuggestionsSetup' });
+
+const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000));
+
+const roles: BubbleListProps['roles'] = {
+  user: {
+    placement: 'end',
+    avatar: { icon: UserOutlined, style: { background: '#87d068' } },
+  },
+  text: {
+    placement: 'start',
+    avatar: { icon: UserOutlined, style: { background: '#fde3cf' } },
+    typing: true,
+  },
+  suggestion: {
+    placement: 'start',
+    avatar: { icon: UserOutlined, style: { visibility: 'hidden' } },
+    variant: 'borderless',
+    messageRender: (content) => h(Prompts, {
+      vertical: true,
+      items: (content as any as string[]).map((text) => ({
+        key: text,
+        icon: h(SmileOutlined, { style: { color: '#FAAD14' } }),
+        description: text,
+      }))
+    }),    
+  },
+};
+
+type AgentUserMessage = {
+  type: 'user';
+  content: string;
+};
+
+type AgentAIMessage = {
+  type: 'ai';
+  content?: string;
+  list?: (
+    | {
+      type: 'text';
+      content: string;
+    }
+    | {
+      type: 'suggestion';
+      content: string[];
+    }
+  )[];
+};
+
+type AgentMessage = AgentUserMessage | AgentAIMessage;
+
+type BubbleMessage = {
+  role: string;
+};
+
+const content = ref('');
+const senderLoading = ref(false);
+
+// Agent for request
+const [ agent ] = useXAgent<AgentMessage>({
+  request: async ({ message }, { onSuccess }) => {
+    senderLoading.value = true;
+    await sleep();
+
+    const { content } = message || {};
+
+    senderLoading.value = false;
+    onSuccess({
+      type: 'ai',
+      list: [
+        {
+          type: 'text',
+          content: `Do you want?`,
+        },
+        {
+          type: 'suggestion',
+          content: [`Look at: ${content}`, `Search: ${content}`, `Try: ${content}`],
+        },
+      ],
+    });
+  },
+});
+
+// Chat messages
+const { onRequest, parsedMessages } = useXChat<AgentMessage, BubbleMessage>({
+  agent: agent.value,
+
+  defaultMessages: [
+    {
+      id: 'init',
+      message: {
+        type: 'ai',
+        content: 'Hello, what can I do for you?',
+      },
+      status: 'success',
+    },
+  ],
+
+  requestPlaceholder: {
+    type: 'ai',
+    content: 'Waiting...',
+  },
+
+  // Convert AgentMessage to BubbleMessage
+  parser: (agentMessages) => {
+    const list = agentMessages.content ? [agentMessages] : (agentMessages as AgentAIMessage).list;
+
+    return (list || []).map((msg) => ({
+      role: msg.type,
+      content: msg.content,
+    }));
+  },
+});
+</script>
+<template>
+  <Flex
+    vertical
+    gap="middle"
+  >
+    <Bubble.List
+      :roles="roles"
+      :style="{ maxHeight: 300 }"
+      :items="parsedMessages.map(({ id, message, status }) => ({
+        key: id,
+        loading: status === 'loading',
+        ...message,
+      }))"
+    />
+    <Sender
+      :loading="senderLoading"
+      :value="content"
+      :on-change="(v) => content = v"
+      :on-submit="(nextContent) => {
+        onRequest({
+          type: 'user',
+          content: nextContent,
+        });
+        content = '';
+      }"
+    />
+  </Flex>
+</template>

+ 40 - 0
docs/examples-setup/welcome/background.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { Card, ConfigProvider, Flex, theme } from 'ant-design-vue';
+import { Welcome } from 'ant-design-x-vue';
+
+defineOptions({ name: 'AXWelcomeBackgroundSetup' });
+
+const items: {
+  algorithm: typeof theme.defaultAlgorithm;
+  background: string;
+}[] = [
+  {
+    algorithm: theme.defaultAlgorithm,
+    background: 'linear-gradient(97deg, #f2f9fe 0%, #f7f3ff 100%)',
+  },
+  {
+    algorithm: theme.darkAlgorithm,
+    background:
+      'linear-gradient(97deg, rgba(90,196,255,0.12) 0%, rgba(174,136,255,0.12) 100%)',
+  },
+];
+</script>
+<template>
+  <Flex vertical>
+    <ConfigProvider
+      v-for="({ algorithm, background }, index) in items"
+      :key="index"
+      :theme="{ algorithm }"
+    >
+      <Card :style="{ borderRadius: 0 }">
+        <Welcome
+          :style="{ backgroundImage: background, borderStartStartRadius: 4 }"
+          icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
+          title="Hello, I'm Ant Design X"
+          description="Base on Ant Design, AGI
+        product interface solution, create a better intelligent vision~"
+        />
+      </Card>
+    </ConfigProvider>
+  </Flex>
+</template>

+ 12 - 0
docs/examples-setup/welcome/basic.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import { Welcome } from 'ant-design-x-vue';
+
+defineOptions({ name: 'AXWelcomeBasicSetup' });
+</script>
+<template>
+  <Welcome
+    icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
+    title="Hello, I'm Ant Design X"
+    description="Base on Ant Design, AGI product interface solution, create a better intelligent vision~"
+  />
+</template>

+ 30 - 0
docs/examples-setup/welcome/variant.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { EllipsisOutlined, ShareAltOutlined } from '@ant-design/icons-vue';
+import { Button, Space } from 'ant-design-vue';
+import { Welcome } from 'ant-design-x-vue';
+
+defineOptions({ name: 'AXWelcomeVariantSetup' });
+</script>
+<template>
+  <Welcome
+    variant="borderless"
+    icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
+    title="Hello, I'm Ant Design X"
+    description="Base on Ant Design, AGI product interface solution, create a better intelligent vision~"
+  >
+    <template #extra>
+      <Space>
+        <Button>
+          <template #icon>
+            <ShareAltOutlined />
+          </template>
+        </Button>
+        <Button>
+          <template #icon>
+            <EllipsisOutlined />
+          </template>
+        </Button>
+      </Space>
+    </template>
+  </Welcome>
+</template>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff