stream-cancel.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <script setup lang="tsx">
  2. import { UserOutlined } from '@ant-design/icons-vue';
  3. import { Flex } from 'ant-design-vue';
  4. import { Bubble, Sender, useXAgent, useXChat, XStream, type BubbleListProps } from 'ant-design-x-vue';
  5. import { onWatcherCleanup, ref, watchEffect } from 'vue';
  6. defineOptions({ name: 'AXUseXChatStreamCancel' });
  7. const roles: BubbleListProps['roles'] = {
  8. ai: {
  9. placement: 'start',
  10. avatar: { icon: <UserOutlined />, style: { background: '#fde3cf' } },
  11. },
  12. local: {
  13. placement: 'end',
  14. avatar: { icon: <UserOutlined />, style: { background: '#87d068' } },
  15. },
  16. };
  17. const contentChunks = [
  18. 'He',
  19. 'llo',
  20. ', w',
  21. 'or',
  22. 'ld!',
  23. ' Ant',
  24. ' Design',
  25. ' X',
  26. ' is',
  27. ' the',
  28. ' best',
  29. '!',
  30. ];
  31. function mockReadableStream() {
  32. const sseChunks: string[] = [];
  33. for (let i = 0; i < contentChunks.length; i++) {
  34. const sseEventPart = `event: message\ndata: {"id":"${i}","content":"${contentChunks[i]}"}\n\n`;
  35. sseChunks.push(sseEventPart);
  36. }
  37. return new ReadableStream({
  38. async start(controller) {
  39. for (const chunk of sseChunks) {
  40. await new Promise((resolve) => setTimeout(resolve, 300));
  41. controller.enqueue(new TextEncoder().encode(chunk));
  42. }
  43. controller.close();
  44. },
  45. });
  46. }
  47. const content = ref('');
  48. const senderLoading = ref(false);
  49. const abort = ref(() => { });
  50. watchEffect(() => {
  51. onWatcherCleanup(() => {
  52. abort.value();
  53. })
  54. });
  55. // Agent for request
  56. const [ agent ] = useXAgent({
  57. request: async (_, { onSuccess, onUpdate }) => {
  58. senderLoading.value = true;
  59. const stream = XStream({
  60. readableStream: mockReadableStream(),
  61. });
  62. const reader = stream.getReader();
  63. abort.value = () => {
  64. reader?.cancel();
  65. };
  66. let current = '';
  67. while (reader) {
  68. const { value, done } = await reader.read();
  69. if (done) {
  70. senderLoading.value = false;
  71. onSuccess(current);
  72. break;
  73. }
  74. if (!value) continue;
  75. const data = JSON.parse(value.data);
  76. current += data.content || '';
  77. onUpdate(current);
  78. }
  79. },
  80. });
  81. // Chat messages
  82. const { onRequest, messages } = useXChat({
  83. agent: agent.value,
  84. });
  85. defineRender(() => {
  86. return (
  87. <Flex vertical gap="middle">
  88. <Bubble.List
  89. roles={roles}
  90. style={{ maxHeight: 300 }}
  91. items={messages.value.map(({ id, message, status }) => ({
  92. key: id,
  93. role: status === 'local' ? 'local' : 'ai',
  94. content: message,
  95. }))}
  96. />
  97. <Sender
  98. loading={senderLoading.value}
  99. value={content.value}
  100. onChange={(v) => content.value = v}
  101. onSubmit={(nextContent) => {
  102. onRequest(nextContent);
  103. content.value = '';
  104. }}
  105. onCancel={() => abort.value()}
  106. />
  107. </Flex>
  108. )
  109. });
  110. </script>