stream-cancel.vue 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <script setup lang="ts">
  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: 'AXUseXChatStreamCancelSetup' });
  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. </script>
  86. <template>
  87. <Flex
  88. vertical
  89. gap="middle"
  90. >
  91. <Bubble.List
  92. :roles="roles"
  93. :style="{ maxHeight: 300 }"
  94. :items="messages.map(({ id, message, status }) => ({
  95. key: id,
  96. role: status === 'local' ? 'local' : 'ai',
  97. content: message,
  98. }))"
  99. />
  100. <Sender
  101. :loading="senderLoading"
  102. :value="content"
  103. :on-change="(v) => content = v"
  104. :on-submit="(nextContent) => {
  105. onRequest(nextContent);
  106. content = '';
  107. }"
  108. :on-cancel="() => abort()"
  109. />
  110. </Flex>
  111. </template>