dou-ui 0.1.0-alpha.3 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/card/card.js.map +1 -1
- package/dist/components/card/card.vue_vue_type_script_setup_true_lang.js +45 -19
- package/dist/components/card/card.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-confirm/chat-confirm.js.map +1 -1
- package/dist/components/chat-confirm/chat-confirm.vue_vue_type_script_setup_true_lang.js +2 -3
- package/dist/components/chat-confirm/chat-confirm.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-input/chat-input.js.map +1 -1
- package/dist/components/chat-input/chat-input.vue_vue_type_script_setup_true_lang.js +75 -7
- package/dist/components/chat-input/chat-input.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-input/chat-input2.js.map +1 -1
- package/dist/components/chat-list/chat-list.js +9 -4
- package/dist/components/chat-list/chat-list.js.map +1 -1
- package/dist/components/chat-list/chat-list.vue_vue_type_script_setup_true_lang.js +34 -0
- package/dist/components/chat-list/chat-list.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-list/chat-list2.js +6 -0
- package/dist/components/chat-list/chat-list2.js.map +1 -0
- package/dist/components/chat-list/index.js +1 -1
- package/dist/components/chat-message/chat-message.js.map +1 -1
- package/dist/components/chat-message/chat-message.vue_vue_type_script_setup_true_lang.js +32 -7
- package/dist/components/chat-message/chat-message.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chat-message/chat-message2.js.map +1 -1
- package/dist/components/chat-message/style.js +1 -0
- package/dist/components/chat-question/chat-question.js +6 -0
- package/dist/components/chat-question/chat-question.js.map +1 -0
- package/dist/components/chat-question/chat-question.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/components/chat-question/chat-question.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/chat-question/index.js +7 -0
- package/dist/components/chat-question/index.js.map +1 -0
- package/dist/components/chat-question/style.d.ts +1 -0
- package/dist/components/chat-question/style.js +2 -0
- package/dist/components/comment-thread/comment-thread.js.map +1 -1
- package/dist/components/comment-thread/comment-thread.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/components/comment-thread/comment-thread.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/config-provider/use-config.js +5 -2
- package/dist/components/config-provider/use-config.js.map +1 -1
- package/dist/components/dialog/functional.js +3 -7
- package/dist/components/dialog/functional.js.map +1 -1
- package/dist/components/dialog/index.js +4 -3
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/image/image.js.map +1 -1
- package/dist/components/image/image.vue_vue_type_script_setup_true_lang.js +10 -1
- package/dist/components/image/image.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/link/link.js.map +1 -1
- package/dist/components/link/link.vue_vue_type_script_setup_true_lang.js +116 -31
- package/dist/components/link/link.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu-actions.js +65 -0
- package/dist/components/menu/menu-actions.js.map +1 -0
- package/dist/components/menu/menu-item.js.map +1 -1
- package/dist/components/menu/menu-item.vue_vue_type_script_setup_true_lang.js +50 -12
- package/dist/components/menu/menu-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu-list.js.map +1 -1
- package/dist/components/menu/menu-list.vue_vue_type_script_setup_true_lang.js +32 -17
- package/dist/components/menu/menu-list.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu-popup.js.map +1 -1
- package/dist/components/menu/menu-popup.vue_vue_type_script_setup_true_lang.js +37 -8
- package/dist/components/menu/menu-popup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu.js +1 -0
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/menu/menu.vue_vue_type_script_setup_true_lang.js +5 -1
- package/dist/components/menu/menu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/menu/menu2.js.map +1 -1
- package/dist/components/node-graph/layout.js +123 -15
- package/dist/components/node-graph/layout.js.map +1 -1
- package/dist/components/toast/index.js +5 -4
- package/dist/components/toast/index.js.map +1 -1
- package/dist/components/toast/store.js +1 -9
- package/dist/components/toast/store.js.map +1 -1
- package/dist/components/virtual-list/virtual-list.js +7 -1
- package/dist/components/virtual-list/virtual-list.js.map +1 -1
- package/dist/components/virtual-list/virtual-list.vue_vue_type_script_setup_true_lang.js +2 -7
- package/dist/components/virtual-list/virtual-list.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/virtual-list/virtual-list2.js.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/locale/en.js +1 -0
- package/dist/locale/en.js.map +1 -1
- package/dist/locale/zh-cn.js +1 -0
- package/dist/locale/zh-cn.js.map +1 -1
- package/dist/locale/zh-tw.js +1 -0
- package/dist/locale/zh-tw.js.map +1 -1
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/components/card/card.d.ts +6 -1
- package/dist/types/components/chat-input/chat-input.d.ts +33 -3
- package/dist/types/components/chat-input/chat-input.vue.d.ts +7 -0
- package/dist/types/components/chat-input/index.d.ts +20 -0
- package/dist/types/components/chat-list/chat-list.d.ts +19 -1
- package/dist/types/components/chat-message/chat-message.d.ts +32 -2
- package/dist/types/components/chat-message/chat-message.vue.d.ts +4 -1
- package/dist/types/components/chat-message/index.d.ts +8 -0
- package/dist/types/components/chat-question/chat-question.d.ts +43 -0
- package/dist/types/components/chat-question/chat-question.vue.d.ts +18 -0
- package/dist/types/components/chat-question/index.d.ts +43 -0
- package/dist/types/components/comment-thread/comment-thread.d.ts +6 -0
- package/dist/types/components/comment-thread/comment-thread.vue.d.ts +1 -0
- package/dist/types/components/comment-thread/index.d.ts +3 -0
- package/dist/types/components/dialog/functional.d.ts +0 -3
- package/dist/types/components/dialog/index.d.ts +1 -1
- package/dist/types/components/image/image.d.ts +9 -0
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/components/link/index.d.ts +3 -0
- package/dist/types/components/link/link.d.ts +24 -3
- package/dist/types/components/link/link.vue.d.ts +1 -0
- package/dist/types/components/menu/index.d.ts +3 -0
- package/dist/types/components/menu/menu-actions.d.ts +26 -0
- package/dist/types/components/menu/menu-item.vue.d.ts +6 -0
- package/dist/types/components/menu/menu.d.ts +32 -0
- package/dist/types/components/menu/menu.vue.d.ts +1 -0
- package/dist/types/components/node-graph/layout.d.ts +26 -0
- package/dist/types/components/select/index.d.ts +3 -3
- package/dist/types/components/select/select.vue.d.ts +1 -1
- package/dist/types/components/toast/index.d.ts +0 -2
- package/dist/types/components/toast/store.d.ts +0 -4
- package/dist/types/components/virtual-list/virtual-list.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/locale/types.d.ts +3 -1
- package/dist/types/utils/app-context.d.ts +14 -0
- package/dist/utils/app-context.js +22 -0
- package/dist/utils/app-context.js.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-input2.js","names":["_sfc_main"],"sources":["../../../src/components/chat-input/chat-input.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ArrowUpIcon, StopIcon } from '@dou-ui/icons'\nimport { computed, nextTick, onMounted, ref, useAttrs, useId, useTemplateRef, watch } from 'vue'\nimport type { StyleValue } from 'vue'\nimport { useFloating } from '../../composables/use-floating'\nimport { useLayer } from '../../composables/use-layer'\nimport { useThemeScope } from '../../composables/use-theme-scope'\nimport { isComposing } from '../../utils/keyboard'\nimport { useNamespace } from '../../utils/namespace'\nimport { useTeleport } from '../../utils/teleport'\nimport { DouButton } from '../button'\nimport { useLocale, useZIndex } from '../config-provider/use-config'\nimport { useFormDisabled } from '../form/use-form-item'\nimport { findToken } from './chat-input'\nimport type {\n ChatInputEmits,\n ChatInputExpose,\n ChatInputOption,\n ChatInputProps,\n ChatInputSlots,\n ChatInputToken,\n} from './chat-input'\n\ndefineOptions({ name: 'DouChatInput', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<ChatInputProps>(), {\n submitOn: 'enter',\n loading: false,\n size: 'md',\n disabled: false,\n minRows: 1,\n maxRows: 8,\n canSubmit: undefined,\n commands: () => [],\n mentions: () => [],\n teleport: undefined,\n})\nconst emit = defineEmits<ChatInputEmits>()\nconst slots = defineSlots<ChatInputSlots>()\nconst model = defineModel<string>({ default: '' })\n\nconst ns = useNamespace('chat-input')\nconst id = useId()\nconst { t } = useLocale()\nconst disabled = useFormDisabled(() => props.disabled)\nconst buttonSize = computed(() => (props.size === 'lg' ? 'md' : 'sm'))\n\n// `class` and `style` dress the frame, the rest goes to the textarea.\nconst attrs = useAttrs()\nconst textareaAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs\n return rest\n})\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>('textarea')\n\nconst submittable = computed(\n () => !disabled.value && (props.canSubmit ?? model.value.trim().length > 0),\n)\n\nfunction resize() {\n const el = textareaRef.value\n if (!el) return\n const style = getComputedStyle(el)\n const lineHeight = Number.parseFloat(style.lineHeight) || 22\n const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\n const min = props.minRows * lineHeight + (padding || 0)\n const max = props.maxRows * lineHeight + (padding || 0)\n el.style.height = 'auto'\n const content = el.scrollHeight\n el.style.height = `${Math.min(Math.max(content, min), max)}px`\n el.style.overflowY = content > max ? 'auto' : 'hidden'\n}\nwatch([model, () => props.minRows, () => props.maxRows], () => nextTick(resize))\nonMounted(resize)\n\n// While a reply is generated the button stops it, unless something new\n// has been typed: then it sends, and the app may queue the message.\nconst stopping = computed(() => props.loading && !submittable.value)\n\nfunction submit() {\n if (!submittable.value) return\n emit('submit', model.value)\n}\n\nfunction stop() {\n if (props.loading) emit('stop')\n}\n\n// --- `/` command and `@` mention menu ---\nconst token = ref<ChatInputToken | null>(null)\nconst dismissed = ref<number | null>(null)\nconst active = ref(0)\n\nfunction readToken() {\n const el = textareaRef.value\n if (!el) return\n const next = findToken(model.value, el.selectionStart ?? model.value.length)\n const current = token.value\n // keyup and click re-read the caret after every key, arrows included: an\n // unchanged token must keep its identity or the menu resets its highlight.\n if (\n next &&\n current &&\n next.trigger === current.trigger &&\n next.start === current.start &&\n next.end === current.end &&\n next.query === current.query\n )\n return\n if (next?.start !== current?.start) dismissed.value = null\n token.value = next\n}\n\nconst options = computed<ChatInputOption[]>(() => {\n const current = token.value\n if (!current || dismissed.value === current.start) return []\n const source = current.trigger === '/' ? props.commands : props.mentions\n const query = current.query.toLowerCase()\n return source.filter(\n (option) =>\n option.value.toLowerCase().includes(query) ||\n (option.label ?? '').toLowerCase().includes(query),\n )\n})\nconst menuOpen = computed(() => !disabled.value && options.value.length > 0)\n\n// The menu is a popup like any other: rendered on top of the page, above\n// the frame (or below it when there is no room), stacked over what opened\n// before and closed by a press outside. The focus stays in the textarea.\nconst rootRef = useTemplateRef<HTMLElement>('root')\nconst menuRef = useTemplateRef<HTMLElement>('menu')\nconst teleportTarget = useTeleport(() => props.teleport)\nconst { style: floatingStyle, placement: menuPlacement } = useFloating(\n () => (menuOpen.value ? rootRef.value : null),\n menuRef,\n { open: menuOpen, placement: 'top-start', offset: 6 },\n)\nconst { next: nextZIndex } = useZIndex()\nconst menuZIndex = ref<number>()\nconst menuWidth = ref<number>()\n// rendered the first time it opens, so the server output stays the same\nconst menuRendered = ref(false)\nconst { theme, sync: syncTheme } = useThemeScope(() => rootRef.value)\nwatch(menuOpen, (value) => {\n if (!value) return\n menuRendered.value = true\n menuZIndex.value = nextZIndex()\n menuWidth.value = rootRef.value?.offsetWidth\n syncTheme()\n})\nconst menuStyle = computed(() => ({\n ...floatingStyle.value,\n zIndex: menuZIndex.value,\n '--dou-chat-input-menu-width': menuWidth.value ? `${menuWidth.value}px` : undefined,\n}))\nuseLayer({\n open: menuOpen,\n elements: () => [rootRef.value, menuRef.value],\n onOutside: () => (token.value = null),\n onEscape: () => (dismissed.value = token.value?.start ?? null),\n})\n// Only a different list moves the highlight back to the first usable option.\nconst firstEnabled = () =>\n Math.max(\n 0,\n options.value.findIndex((option) => !option.disabled),\n )\nwatch(\n () =>\n menuOpen.value\n ? `${token.value?.trigger}${options.value.map((option) => option.value).join('\\u0000')}`\n : '',\n () => (active.value = firstEnabled()),\n)\n\nfunction move(step: 1 | -1) {\n const list = options.value\n for (let offset = 1; offset <= list.length; offset++) {\n const index = (((active.value + step * offset) % list.length) + list.length) % list.length\n if (!list[index]!.disabled) {\n active.value = index\n return\n }\n }\n}\nconst optionId = (index: number) => `${id}-option-${index}`\nwatch(active, async (index) => {\n await nextTick()\n document.getElementById(optionId(index))?.scrollIntoView?.({ block: 'nearest' })\n})\n\nfunction pick(option: ChatInputOption) {\n const current = token.value\n if (!current || option.disabled) return\n const before = model.value.slice(0, current.start)\n const after = model.value.slice(current.end)\n let caret: number\n if (current.trigger === '/') {\n model.value = before + after.replace(/^\\s+/, '')\n caret = before.length\n emit('command', option)\n } else {\n const inserted = `@${option.value} `\n model.value = before + inserted + after.replace(/^\\s+/, '')\n caret = before.length + inserted.length\n emit('mention', option)\n }\n token.value = null\n void nextTick(() => {\n textareaRef.value?.focus()\n textareaRef.value?.setSelectionRange(caret, caret)\n })\n}\n\nfunction onMenuKeydown(event: KeyboardEvent): boolean {\n if (!menuOpen.value) return false\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault()\n move(event.key === 'ArrowDown' ? 1 : -1)\n return true\n }\n if ((event.key === 'Enter' || event.key === 'Tab') && !isComposing(event)) {\n event.preventDefault()\n pick(options.value[active.value]!)\n return true\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n dismissed.value = token.value?.start ?? null\n return true\n }\n return false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (onMenuKeydown(event)) return\n if (event.key !== 'Enter' || isComposing(event)) return\n const mod = event.ctrlKey || event.metaKey\n const sends = props.submitOn === 'enter' ? !event.shiftKey && !mod : mod\n if (!sends) return\n event.preventDefault()\n submit()\n}\n\nconst filesOf = (list: DataTransferItemList | undefined) =>\n [...(list ?? [])]\n .filter((item) => item.kind === 'file')\n .map((item) => item.getAsFile())\n .filter((file): file is File => !!file)\n\nfunction onPaste(event: ClipboardEvent) {\n const files = filesOf(event.clipboardData?.items)\n if (!files.length) return\n // keep the file's name from being pasted as text\n event.preventDefault()\n emit('attach', files)\n}\n\nconst dragging = ref(false)\nlet depth = 0\nconst carriesFiles = (event: DragEvent) => !!event.dataTransfer?.types.includes('Files')\n\nfunction onDragenter(event: DragEvent) {\n if (disabled.value || !carriesFiles(event)) return\n depth++\n dragging.value = true\n}\nfunction onDragover(event: DragEvent) {\n if (disabled.value || !carriesFiles(event)) return\n event.preventDefault()\n}\nfunction onDragleave() {\n depth = Math.max(0, depth - 1)\n if (!depth) dragging.value = false\n}\nfunction onDrop(event: DragEvent) {\n depth = 0\n dragging.value = false\n if (disabled.value) return\n const files = [...(event.dataTransfer?.files ?? [])]\n if (!files.length) return\n event.preventDefault()\n emit('attach', files)\n}\n\n// A press anywhere on the frame that is not a control puts the caret in the text.\nfunction onMousedown(event: MouseEvent) {\n const target = event.target as HTMLElement\n if (target === textareaRef.value || target.closest('button, a, input, select, [tabindex]')) return\n event.preventDefault()\n textareaRef.value?.focus()\n}\n\ndefineExpose<ChatInputExpose>({\n get textarea() {\n return textareaRef.value\n },\n focus: () => textareaRef.value?.focus(),\n blur: () => textareaRef.value?.blur(),\n submit,\n})\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"[\n ns.b(),\n ns.m(size),\n ns.is('disabled', disabled),\n ns.is('loading', loading),\n ns.is('dragging', dragging),\n attrs.class,\n ]\"\n :style=\"attrs.style as StyleValue\"\n @mousedown=\"onMousedown\"\n @dragenter=\"onDragenter\"\n @dragover=\"onDragover\"\n @dragleave=\"onDragleave\"\n @drop=\"onDrop\"\n >\n <div v-if=\"slots.header\" :class=\"ns.e('header')\"><slot name=\"header\" /></div>\n <textarea\n ref=\"textarea\"\n v-model=\"model\"\n :aria-label=\"placeholder ?? t('chat.placeholder')\"\n v-bind=\"textareaAttrs\"\n :class=\"ns.e('native')\"\n :rows=\"minRows\"\n :placeholder=\"placeholder ?? t('chat.placeholder')\"\n :disabled=\"disabled\"\n :aria-autocomplete=\"commands.length || mentions.length ? 'list' : undefined\"\n :aria-expanded=\"commands.length || mentions.length ? menuOpen : undefined\"\n :aria-controls=\"menuOpen ? `${id}-menu` : undefined\"\n :aria-activedescendant=\"menuOpen ? optionId(active) : undefined\"\n @keydown=\"onKeydown\"\n @keyup=\"readToken\"\n @click=\"readToken\"\n @input=\"readToken\"\n @blur=\"token = null\"\n @paste=\"onPaste\"\n />\n <Teleport v-if=\"menuRendered\" :to=\"teleportTarget.to\" :disabled=\"teleportTarget.disabled\">\n <Transition name=\"dou-floating\">\n <ul\n v-if=\"menuOpen\"\n :id=\"`${id}-menu`\"\n ref=\"menu\"\n :class=\"[ns.e('menu'), 'dou-floating']\"\n :style=\"menuStyle\"\n :data-placement=\"menuPlacement\"\n :data-dou-theme=\"theme\"\n role=\"listbox\"\n @mousedown.prevent\n >\n <li\n v-for=\"(option, index) in options\"\n :id=\"optionId(index)\"\n :key=\"option.value\"\n role=\"option\"\n :class=\"[\n ns.e('option'),\n ns.is('active', index === active),\n ns.is('disabled', !!option.disabled),\n ]\"\n :aria-selected=\"index === active\"\n :aria-disabled=\"option.disabled || undefined\"\n @mouseenter=\"!option.disabled && (active = index)\"\n @click=\"pick(option)\"\n >\n <slot name=\"option\" :option=\"option\" :trigger=\"token!.trigger\">\n <component :is=\"option.icon\" v-if=\"option.icon\" :class=\"ns.e('option-icon')\" />\n <span :class=\"ns.e('option-label')\">\n {{ token?.trigger === '/' ? '/' : '@' }}{{ option.label ?? option.value }}\n </span>\n <span v-if=\"option.description\" :class=\"ns.e('option-description')\">\n {{ option.description }}\n </span>\n </slot>\n </li>\n </ul>\n </Transition>\n </Teleport>\n <div :class=\"ns.e('footer')\">\n <div :class=\"ns.e('actions')\"><slot name=\"actions\" /></div>\n <slot\n name=\"submit\"\n :submit=\"submit\"\n :stop=\"stop\"\n :loading=\"loading\"\n :disabled=\"stopping ? false : !submittable\"\n >\n <DouButton\n v-if=\"stopping\"\n :class=\"ns.e('submit')\"\n :size=\"buttonSize\"\n :aria-label=\"t('chat.stop')\"\n @click=\"stop\"\n >\n <StopIcon />\n </DouButton>\n <DouButton\n v-else\n :class=\"ns.e('submit')\"\n :size=\"buttonSize\"\n :disabled=\"!submittable\"\n :aria-label=\"t('chat.send')\"\n @click=\"submit\"\n >\n <ArrowUpIcon />\n </DouButton>\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"chat-input2.js","names":["_sfc_main"],"sources":["../../../src/components/chat-input/chat-input.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ArrowUpIcon, StopIcon } from '@dou-ui/icons'\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onBeforeUnmount,\n onMounted,\n reactive,\n ref,\n useAttrs,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\nimport type { StyleValue } from 'vue'\nimport { useFloating } from '../../composables/use-floating'\nimport { useLayer } from '../../composables/use-layer'\nimport { useThemeScope } from '../../composables/use-theme-scope'\nimport { isComposing } from '../../utils/keyboard'\nimport { useNamespace } from '../../utils/namespace'\nimport { useTeleport } from '../../utils/teleport'\nimport { DouButton } from '../button'\nimport { useLocale, useZIndex } from '../config-provider/use-config'\nimport { useFormDisabled } from '../form/use-form-item'\nimport { findToken } from './chat-input'\nimport type {\n ChatInputEmits,\n ChatInputExpose,\n ChatInputOption,\n ChatInputProps,\n ChatInputSlots,\n ChatInputToken,\n} from './chat-input'\n\ndefineOptions({ name: 'DouChatInput', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<ChatInputProps>(), {\n submitOn: 'enter',\n loading: false,\n size: 'md',\n disabled: false,\n minRows: 1,\n maxRows: 8,\n canSubmit: undefined,\n commands: () => [],\n mentions: () => [],\n filter: true,\n searchDelay: 300,\n teleport: undefined,\n})\nconst emit = defineEmits<ChatInputEmits>()\nconst slots = defineSlots<ChatInputSlots>()\nconst model = defineModel<string>({ default: '' })\n\nconst ns = useNamespace('chat-input')\nconst id = useId()\nconst { t } = useLocale()\nconst disabled = useFormDisabled(() => props.disabled)\nconst buttonSize = computed(() => (props.size === 'lg' ? 'md' : 'sm'))\n\n// `class` and `style` dress the frame, the rest goes to the textarea.\nconst attrs = useAttrs()\nconst textareaAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs\n return rest\n})\n\nconst textareaRef = useTemplateRef<HTMLTextAreaElement>('textarea')\n\nconst submittable = computed(\n () => !disabled.value && (props.canSubmit ?? model.value.trim().length > 0),\n)\n\nfunction resize() {\n const el = textareaRef.value\n if (!el) return\n const style = getComputedStyle(el)\n const lineHeight = Number.parseFloat(style.lineHeight) || 22\n const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\n const min = props.minRows * lineHeight + (padding || 0)\n const max = props.maxRows * lineHeight + (padding || 0)\n el.style.height = 'auto'\n const content = el.scrollHeight\n el.style.height = `${Math.min(Math.max(content, min), max)}px`\n el.style.overflowY = content > max ? 'auto' : 'hidden'\n}\nwatch([model, () => props.minRows, () => props.maxRows], () => nextTick(resize))\nonMounted(resize)\n\n// While a reply is generated the button stops it, unless something new\n// has been typed: then it sends, and the app may queue the message.\nconst stopping = computed(() => props.loading && !submittable.value)\n\nfunction submit() {\n if (!submittable.value) return\n emit('submit', model.value)\n}\n\nfunction stop() {\n if (props.loading) emit('stop')\n}\n\n// --- `/` command and `@` mention menu ---\nconst token = ref<ChatInputToken | null>(null)\nconst dismissed = ref<number | null>(null)\nconst active = ref(0)\n\n// While an input method composes, the text holds what is being spelled\n// (pinyin, say), not what will be typed: wait for it to finish.\nlet composing = false\nfunction onCaret(event?: Event) {\n if (composing || (event instanceof KeyboardEvent && isComposing(event))) return\n if ((event as InputEvent | undefined)?.isComposing) return\n readToken()\n}\nfunction onCompositionStart() {\n composing = true\n}\nfunction onCompositionEnd() {\n composing = false\n readToken()\n}\n\nfunction readToken() {\n const el = textareaRef.value\n if (!el) return\n // the textarea's own text: the model may not have caught up yet, e.g. right\n // after an input method puts its text down\n const next = findToken(el.value, el.selectionStart ?? el.value.length)\n const current = token.value\n // keyup and click re-read the caret after every key, arrows included: an\n // unchanged token must keep its identity or the menu resets its highlight.\n if (\n next &&\n current &&\n next.trigger === current.trigger &&\n next.start === current.start &&\n next.end === current.end &&\n next.query === current.query\n )\n return\n if (next?.start !== current?.start) dismissed.value = null\n token.value = next\n}\n\nconst filterOf = (trigger: '/' | '@') =>\n typeof props.filter === 'object' ? (props.filter[trigger] ?? true) : props.filter\n\nconst options = computed<ChatInputOption[]>(() => {\n const current = token.value\n if (!current || dismissed.value === current.start) return []\n const source = current.trigger === '/' ? props.commands : props.mentions\n const filter = filterOf(current.trigger)\n if (filter === false || (filter === true && !current.query)) return source\n if (typeof filter === 'function')\n return source.filter((option) => filter(option, current.query, current.trigger))\n // what the person sees: the label, a mention's value (it gets inserted), the description\n const query = current.query.toLowerCase()\n const texts = (option: ChatInputOption) => [\n option.label ?? option.value,\n ...(current.trigger === '@' && option.label ? [option.value] : []),\n option.description ?? '',\n ...(option.keywords ?? []),\n ]\n return source.filter((option) => texts(option).some((text) => text.toLowerCase().includes(query)))\n})\n\n// Without local matching, a list the parent puts in after a `search` is the\n// answer to the last query handed out. While what is typed now differs from\n// that query (its search still waits for `searchDelay`, or its answer is on\n// the way), the list shown answers an older one: it stays in view, but\n// Enter and Tab must not pick from it. A list put in before any search,\n// e.g. loaded once after mounting, is fixed and never stale. Nor is any\n// list when nobody listens to `search`: with no one to answer, a parent\n// that passes a new array each render is not answering a query.\nconst instance = getCurrentInstance()\nconst listening = () => !!instance?.vnode.props?.onSearch\ntype Query = { start: number; query: string }\nconst asked: { '/'?: Query; '@'?: Query } = {}\nconst answered = reactive<{ '/'?: Query; '@'?: Query }>({})\nwatch(\n () => props.commands,\n () => (answered['/'] = asked['/']),\n)\nwatch(\n () => props.mentions,\n () => (answered['@'] = asked['@']),\n)\nconst stale = computed(() => {\n const current = token.value\n if (!current || filterOf(current.trigger) !== false || !listening()) return false\n const last = answered[current.trigger]\n return !!last && (last.start !== current.start || last.query !== current.query)\n})\n\n// Each new query is handed out once typing pauses, to look options up\n// elsewhere; the same text at another `@` is a new query too.\nlet searchTimer: ReturnType<typeof setTimeout> | undefined\nwatch(\n () => (token.value ? `${token.value.start}${token.value.trigger}${token.value.query}` : null),\n (key) => {\n clearTimeout(searchTimer)\n const current = token.value\n // Escape closed this one: nothing to look up until another is typed\n if (key === null || !current || dismissed.value === current.start) return\n searchTimer = setTimeout(() => {\n if (listening()) asked[current.trigger] = { start: current.start, query: current.query }\n emit('search', current.query, current.trigger)\n }, props.searchDelay)\n },\n)\nonBeforeUnmount(() => clearTimeout(searchTimer))\nconst menuOpen = computed(() => !disabled.value && options.value.length > 0)\n\n// The menu is a popup like any other: rendered on top of the page, above\n// the frame (or below it when there is no room), stacked over what opened\n// before and closed by a press outside. The focus stays in the textarea.\nconst rootRef = useTemplateRef<HTMLElement>('root')\nconst menuRef = useTemplateRef<HTMLElement>('menu')\nconst teleportTarget = useTeleport(() => props.teleport)\nconst { style: floatingStyle, placement: menuPlacement } = useFloating(\n () => (menuOpen.value ? rootRef.value : null),\n menuRef,\n { open: menuOpen, placement: 'top-start', offset: 6 },\n)\nconst { next: nextZIndex } = useZIndex()\nconst menuZIndex = ref<number>()\nconst menuWidth = ref<number>()\n// rendered the first time it opens, so the server output stays the same\nconst menuRendered = ref(false)\nconst { theme, sync: syncTheme } = useThemeScope(() => rootRef.value)\nwatch(menuOpen, (value) => {\n if (!value) return\n menuRendered.value = true\n menuZIndex.value = nextZIndex()\n menuWidth.value = rootRef.value?.offsetWidth\n syncTheme()\n})\nconst menuStyle = computed(() => ({\n ...floatingStyle.value,\n zIndex: menuZIndex.value,\n '--dou-chat-input-menu-width': menuWidth.value ? `${menuWidth.value}px` : undefined,\n}))\nuseLayer({\n open: menuOpen,\n elements: () => [rootRef.value, menuRef.value],\n onOutside: () => (token.value = null),\n onEscape: () => (dismissed.value = token.value?.start ?? null),\n})\n// Only a different list moves the highlight back to the first usable option.\nconst firstEnabled = () =>\n Math.max(\n 0,\n options.value.findIndex((option) => !option.disabled),\n )\nwatch(\n () =>\n menuOpen.value\n ? `${token.value?.trigger}${options.value.map((option) => option.value).join('\\u0000')}`\n : '',\n () => (active.value = firstEnabled()),\n)\n\nfunction move(step: 1 | -1) {\n const list = options.value\n for (let offset = 1; offset <= list.length; offset++) {\n const index = (((active.value + step * offset) % list.length) + list.length) % list.length\n if (!list[index]!.disabled) {\n active.value = index\n return\n }\n }\n}\nconst optionId = (index: number) => `${id}-option-${index}`\nwatch(active, async (index) => {\n await nextTick()\n document.getElementById(optionId(index))?.scrollIntoView?.({ block: 'nearest' })\n})\n\nfunction pick(option: ChatInputOption) {\n const current = token.value\n if (!current || option.disabled) return\n const before = model.value.slice(0, current.start)\n const after = model.value.slice(current.end)\n let caret: number\n if (current.trigger === '/') {\n model.value = before + after.replace(/^\\s+/, '')\n caret = before.length\n emit('command', option)\n } else {\n const inserted = `@${option.value} `\n model.value = before + inserted + after.replace(/^\\s+/, '')\n caret = before.length + inserted.length\n emit('mention', option)\n }\n token.value = null\n void nextTick(() => {\n textareaRef.value?.focus()\n textareaRef.value?.setSelectionRange(caret, caret)\n })\n}\n\nfunction onMenuKeydown(event: KeyboardEvent): boolean {\n if (!menuOpen.value) return false\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault()\n move(event.key === 'ArrowDown' ? 1 : -1)\n return true\n }\n if ((event.key === 'Enter' || event.key === 'Tab') && !isComposing(event)) {\n event.preventDefault()\n // the results for what is typed now are on their way: wait for them\n if (stale.value) return true\n pick(options.value[active.value]!)\n return true\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n dismissed.value = token.value?.start ?? null\n return true\n }\n return false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (onMenuKeydown(event)) return\n if (event.key !== 'Enter' || isComposing(event)) return\n const mod = event.ctrlKey || event.metaKey\n const sends = props.submitOn === 'enter' ? !event.shiftKey && !mod : mod\n if (!sends) return\n event.preventDefault()\n submit()\n}\n\nconst filesOf = (list: DataTransferItemList | undefined) =>\n [...(list ?? [])]\n .filter((item) => item.kind === 'file')\n .map((item) => item.getAsFile())\n .filter((file): file is File => !!file)\n\nfunction onPaste(event: ClipboardEvent) {\n const files = filesOf(event.clipboardData?.items)\n if (!files.length) return\n // keep the file's name from being pasted as text\n event.preventDefault()\n emit('attach', files)\n}\n\nconst dragging = ref(false)\nlet depth = 0\nconst carriesFiles = (event: DragEvent) => !!event.dataTransfer?.types.includes('Files')\n\nfunction onDragenter(event: DragEvent) {\n if (disabled.value || !carriesFiles(event)) return\n depth++\n dragging.value = true\n}\nfunction onDragover(event: DragEvent) {\n if (disabled.value || !carriesFiles(event)) return\n event.preventDefault()\n}\nfunction onDragleave() {\n depth = Math.max(0, depth - 1)\n if (!depth) dragging.value = false\n}\nfunction onDrop(event: DragEvent) {\n depth = 0\n dragging.value = false\n if (disabled.value) return\n const files = [...(event.dataTransfer?.files ?? [])]\n if (!files.length) return\n event.preventDefault()\n emit('attach', files)\n}\n\n// A press anywhere on the frame that is not a control puts the caret in the text.\nfunction onMousedown(event: MouseEvent) {\n const target = event.target as HTMLElement\n if (target === textareaRef.value || target.closest('button, a, input, select, [tabindex]')) return\n event.preventDefault()\n textareaRef.value?.focus()\n}\n\ndefineExpose<ChatInputExpose>({\n get textarea() {\n return textareaRef.value\n },\n focus: () => textareaRef.value?.focus(),\n blur: () => textareaRef.value?.blur(),\n submit,\n})\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"[\n ns.b(),\n ns.m(size),\n ns.is('disabled', disabled),\n ns.is('loading', loading),\n ns.is('dragging', dragging),\n attrs.class,\n ]\"\n :style=\"attrs.style as StyleValue\"\n @mousedown=\"onMousedown\"\n @dragenter=\"onDragenter\"\n @dragover=\"onDragover\"\n @dragleave=\"onDragleave\"\n @drop=\"onDrop\"\n >\n <div v-if=\"slots.header\" :class=\"ns.e('header')\"><slot name=\"header\" /></div>\n <textarea\n ref=\"textarea\"\n v-model=\"model\"\n :aria-label=\"placeholder ?? t('chat.placeholder')\"\n v-bind=\"textareaAttrs\"\n :class=\"ns.e('native')\"\n :rows=\"minRows\"\n :placeholder=\"placeholder ?? t('chat.placeholder')\"\n :disabled=\"disabled\"\n :aria-autocomplete=\"commands.length || mentions.length ? 'list' : undefined\"\n :aria-expanded=\"commands.length || mentions.length ? menuOpen : undefined\"\n :aria-controls=\"menuOpen ? `${id}-menu` : undefined\"\n :aria-activedescendant=\"menuOpen ? optionId(active) : undefined\"\n @keydown=\"onKeydown\"\n @keyup=\"onCaret\"\n @click=\"onCaret\"\n @input=\"onCaret\"\n @compositionstart=\"onCompositionStart\"\n @compositionend=\"onCompositionEnd\"\n @blur=\"token = null\"\n @paste=\"onPaste\"\n />\n <Teleport v-if=\"menuRendered\" :to=\"teleportTarget.to\" :disabled=\"teleportTarget.disabled\">\n <Transition name=\"dou-floating\">\n <ul\n v-if=\"menuOpen\"\n :id=\"`${id}-menu`\"\n ref=\"menu\"\n :class=\"[ns.e('menu'), 'dou-floating', ns.is('stale', stale)]\"\n :aria-busy=\"stale || undefined\"\n :style=\"menuStyle\"\n :data-placement=\"menuPlacement\"\n :data-dou-theme=\"theme\"\n role=\"listbox\"\n @mousedown.prevent\n >\n <li\n v-for=\"(option, index) in options\"\n :id=\"optionId(index)\"\n :key=\"option.value\"\n role=\"option\"\n :class=\"[\n ns.e('option'),\n ns.is('active', index === active),\n ns.is('disabled', !!option.disabled),\n ]\"\n :aria-selected=\"index === active\"\n :aria-disabled=\"option.disabled || undefined\"\n @mouseenter=\"!option.disabled && (active = index)\"\n @click=\"pick(option)\"\n >\n <slot name=\"option\" :option=\"option\" :trigger=\"token!.trigger\">\n <component :is=\"option.icon\" v-if=\"option.icon\" :class=\"ns.e('option-icon')\" />\n <span :class=\"ns.e('option-label')\">\n {{ token?.trigger === '/' ? '/' : '@' }}{{ option.label ?? option.value }}\n </span>\n <span v-if=\"option.description\" :class=\"ns.e('option-description')\">\n {{ option.description }}\n </span>\n </slot>\n </li>\n </ul>\n </Transition>\n </Teleport>\n <div :class=\"ns.e('footer')\">\n <div :class=\"ns.e('actions')\"><slot name=\"actions\" /></div>\n <slot\n name=\"submit\"\n :submit=\"submit\"\n :stop=\"stop\"\n :loading=\"loading\"\n :disabled=\"stopping ? false : !submittable\"\n >\n <DouButton\n v-if=\"stopping\"\n :class=\"ns.e('submit')\"\n :size=\"buttonSize\"\n :aria-label=\"t('chat.stop')\"\n @click=\"stop\"\n >\n <StopIcon />\n </DouButton>\n <DouButton\n v-else\n :class=\"ns.e('submit')\"\n :size=\"buttonSize\"\n :disabled=\"!submittable\"\n :aria-label=\"t('chat.send')\"\n @click=\"submit\"\n >\n <ArrowUpIcon />\n </DouButton>\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
|
|
1
|
+
// src/components/chat-list/chat-list.ts
|
|
2
|
+
/** @internal Whether the target is an element or a component, not a key. */
|
|
3
|
+
var isTarget = (target) => typeof target === "object" && target !== null;
|
|
4
|
+
/** @internal The element of a target: a component gives its root element. */
|
|
5
|
+
function elementOf(target) {
|
|
6
|
+
const el = "$el" in target ? target.$el : target;
|
|
7
|
+
return el instanceof HTMLElement ? el : null;
|
|
8
|
+
}
|
|
9
|
+
export { elementOf, isTarget };
|
|
5
10
|
|
|
6
11
|
//# sourceMappingURL=chat-list.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-list.js","names":[
|
|
1
|
+
{"version":3,"file":"chat-list.js","names":[],"sources":["../../../src/components/chat-list/chat-list.ts"],"sourcesContent":["import type { ComponentPublicInstance } from 'vue'\nimport type { VirtualListAlign, VirtualListKey } from '../virtual-list/virtual-list'\n\n/**\n * A message to scroll to: its key with `items`; otherwise its element, or\n * the component that renders it (a `ref` on DouChatMessage).\n */\nexport type ChatListTarget = VirtualListKey | Element | ComponentPublicInstance\n\n/** @internal Whether the target is an element or a component, not a key. */\nexport const isTarget = (target: ChatListTarget): target is Element | ComponentPublicInstance =>\n typeof target === 'object' && target !== null\n\n/** @internal The element of a target: a component gives its root element. */\nexport function elementOf(target: Element | ComponentPublicInstance): HTMLElement | null {\n const el = '$el' in target ? (target.$el as unknown) : target\n return el instanceof HTMLElement ? el : null\n}\n\nexport interface ChatListProps<T = any> {\n /**\n * Follow new content: while the list is scrolled to the bottom, it stays\n * there as messages arrive and text streams in. Scrolling up pauses\n * following until the bottom is reached again.\n */\n autoScroll?: boolean\n /** How close to the bottom, in px, still counts as at the bottom. */\n threshold?: number\n /** Show a button that scrolls back down while the list is away from the bottom. */\n showScrollButton?: boolean\n /**\n * The messages as data, drawn one by one with the `item` slot. Only the\n * messages in or near view are in the page, so long conversations stay\n * light; each is measured once rendered.\n */\n items?: T[]\n /** With `items`, a property of each message, or a function, that gives it a unique key. */\n itemKey?: string | ((item: T, index: number) => VirtualListKey)\n /** With `items`, the height in px to assume for a message not rendered yet. */\n estimateSize?: number\n}\n\nexport interface ChatListEmits {\n /** Whether the list is at the bottom changed. */\n atBottomChange: [atBottom: boolean]\n /** Scrolled to the very top, e.g. to load earlier messages. */\n topReached: []\n}\n\nexport interface ChatListSlots<T = any> {\n /** The messages, usually `DouChatBubble`s. */\n default?: () => unknown\n /** With `items`, one message. */\n item?: (props: { item: T; index: number }) => unknown\n /** Replaces the scroll-to-bottom button. */\n button?: (props: { scrollToBottom: () => void }) => unknown\n}\n\nexport interface ChatListExpose {\n /** The scrolling element. */\n viewport: HTMLElement | null\n /** Scroll to the end and follow new content again. */\n scrollToBottom: (behavior?: ScrollBehavior) => void\n /**\n * Jump to one message, e.g. one found by a search, and stop following\n * new content. With `items` give its key (as `itemKey` makes it, or its\n * index without one); otherwise its element or its DouChatMessage. Pair\n * it with the message's `highlighted` to show which one it is. A target\n * not in the list is ignored. `align` defaults to `center`.\n */\n scrollToMessage: (target: ChatListTarget | null | undefined, align?: VirtualListAlign) => void\n /** Whether the list is at the bottom now. */\n readonly atBottom: boolean\n}\n"],"mappings":";;AAUA,IAAa,YAAY,WACvB,OAAO,WAAW,YAAY,WAAW;;AAG3C,SAAgB,UAAU,QAA+D;CACvF,MAAM,KAAK,SAAS,SAAU,OAAO,MAAkB;CACvD,OAAO,cAAc,cAAc,KAAK;AAC1C"}
|
|
@@ -2,8 +2,11 @@ import { useNamespace } from "../../utils/namespace.js";
|
|
|
2
2
|
import { scrollPositionOf } from "../../utils/scroll.js";
|
|
3
3
|
import { useLocale } from "../config-provider/use-config.js";
|
|
4
4
|
import { useReducedMotion } from "../../composables/use-motion.js";
|
|
5
|
+
import { devWarn } from "../../utils/status-check.js";
|
|
6
|
+
import { virtualItemKey } from "../virtual-list/virtual-list.js";
|
|
5
7
|
import { useTouchScroll } from "../../composables/use-touch-scroll.js";
|
|
6
8
|
import { DouVirtualList } from "../virtual-list/index.js";
|
|
9
|
+
import { elementOf, isTarget } from "./chat-list.js";
|
|
7
10
|
import { Transition, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderSlot, unref, useTemplateRef, watch, withCtx } from "vue";
|
|
8
11
|
import { ArrowDownIcon } from "@dou-ui/icons";
|
|
9
12
|
// src/components/chat-list/chat-list.vue?vue&type=script&setup=true&lang.ts
|
|
@@ -143,11 +146,42 @@ var chat_list_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
143
146
|
if (content) observer.observe(content);
|
|
144
147
|
}
|
|
145
148
|
watch(() => props.items?.length, observeContent, { flush: "post" });
|
|
149
|
+
function scrollToMessage(target, align = "center") {
|
|
150
|
+
if (target == null) return;
|
|
151
|
+
if (isTarget(target)) {
|
|
152
|
+
const el = viewportRef.value;
|
|
153
|
+
const node = elementOf(target);
|
|
154
|
+
if (!el || !node || !el.contains(node)) return;
|
|
155
|
+
const box = el.getBoundingClientRect();
|
|
156
|
+
const item = node.getBoundingClientRect();
|
|
157
|
+
const top = item.top - box.top + el.scrollTop;
|
|
158
|
+
const offsets = {
|
|
159
|
+
start: top,
|
|
160
|
+
end: top + item.height - el.clientHeight
|
|
161
|
+
};
|
|
162
|
+
let to = top - (el.clientHeight - item.height) / 2;
|
|
163
|
+
if (align === "start") to = offsets.start;
|
|
164
|
+
else if (align === "end") to = offsets.end;
|
|
165
|
+
else if (align === "nearest") to = item.top < box.top ? offsets.start : item.bottom > box.bottom ? offsets.end : el.scrollTop;
|
|
166
|
+
following = false;
|
|
167
|
+
el.scrollTop = Math.max(0, to);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!props.items) {
|
|
171
|
+
if (process.env.NODE_ENV !== "production") devWarn("[dou-ui] DouChatList: `scrollToMessage` takes a key only with `items`; with the default slot, pass the message’s element or component.");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const index = props.items.findIndex((item, at) => virtualItemKey(props.itemKey, item, at) === target);
|
|
175
|
+
if (index < 0) return;
|
|
176
|
+
following = false;
|
|
177
|
+
listRef.value?.scrollToIndex(index, align);
|
|
178
|
+
}
|
|
146
179
|
__expose({
|
|
147
180
|
get viewport() {
|
|
148
181
|
return viewportRef.value;
|
|
149
182
|
},
|
|
150
183
|
scrollToBottom,
|
|
184
|
+
scrollToMessage,
|
|
151
185
|
get atBottom() {
|
|
152
186
|
return atBottom.value;
|
|
153
187
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-list.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createBlock","_withCtx","_renderSlot","_createElementVNode","_createVNode","_Transition","_createCommentVNode"],"sources":["../../../src/components/chat-list/chat-list.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport type { HTMLAttributes } from 'vue'\nimport type { ForwardedAttributes } from '../../utils/install'\nimport { ArrowDownIcon } from '@dou-ui/icons'\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { DouVirtualList } from '../virtual-list'\nimport type { VirtualListExpose } from '../virtual-list'\nimport { useReducedMotion } from '../../composables/use-motion'\nimport { useTouchScroll } from '../../composables/use-touch-scroll'\nimport { scrollPositionOf } from '../../utils/scroll'\nimport { useNamespace } from '../../utils/namespace'\nimport { useLocale } from '../config-provider/use-config'\nimport type { ChatListEmits, ChatListExpose, ChatListProps, ChatListSlots } from './chat-list'\n\ndefineOptions({ name: 'DouChatList' })\n\nconst props = withDefaults(\n defineProps<\n ChatListProps<T> &\n /* @vue-ignore */ ForwardedAttributes<\n HTMLAttributes,\n ChatListProps<T>,\n keyof ChatListEmits & string\n >\n >(),\n {\n autoScroll: true,\n threshold: 24,\n showScrollButton: true,\n items: undefined,\n itemKey: undefined,\n estimateSize: 120,\n },\n)\nconst emit = defineEmits<ChatListEmits>()\ndefineSlots<ChatListSlots<T>>()\n\nconst ns = useNamespace('chat-list')\nconst reducedMotion = useReducedMotion()\nconst { t } = useLocale()\n\nconst plainViewport = useTemplateRef<HTMLElement>('viewport')\nconst plainContent = useTemplateRef<HTMLElement>('content')\nconst listRef = useTemplateRef<VirtualListExpose>('list')\n// With `items`, the virtual list scrolls and its spacer holds the content.\nconst viewportRef = {\n get value() {\n return props.items ? (listRef.value?.el() ?? null) : plainViewport.value\n },\n}\nconst contentRef = {\n get value() {\n return props.items\n ? (viewportRef.value?.firstElementChild as HTMLElement | null | undefined)\n : plainContent.value\n },\n}\n\nconst atBottom = ref(true)\nwatch(atBottom, (value) => emit('atBottomChange', value))\n\n// Following is a decision of the reader: scrolling up turns it off,\n// reaching the bottom turns it on again. Growing content alone never\n// changes it.\nlet following = true\nlet lastTop = 0\nlet atTop = false\nconst touch = useTouchScroll()\n\nfunction distanceToBottom(el: HTMLElement) {\n return el.scrollHeight - el.scrollTop - el.clientHeight\n}\n\nconst isTop = (el: HTMLElement) =>\n scrollPositionOf(el).top <= 0 && el.scrollHeight > el.clientHeight\n\nfunction onScroll() {\n const el = viewportRef.value\n if (!el) return\n const near = distanceToBottom(el) <= props.threshold\n if (near) following = true\n else if (el.scrollTop < lastTop) following = false\n lastTop = el.scrollTop\n atBottom.value = near\n if (!isTop(el)) {\n atTop = false\n setTopAnchor(el, undefined)\n } else if (!atTop) touch.whenSettled(reachTop)\n}\n\n// Earlier messages loaded on top keep what is in view where it was. On iOS\n// the top is reached while the content still bounces; a position written\n// then is dropped, so `topReached` waits until the scroll is over.\nfunction reachTop() {\n const el = viewportRef.value\n if (!el || atTop || !isTop(el)) return\n atTop = true\n // the virtual list keeps its own first item in place\n if (!props.items) setTopAnchor(el, anchorOf())\n emit('topReached')\n}\n\n/** The first message, and how far it is from the top of the messages. */\nlet topAnchor: { node: Element; y: number } | undefined\n/** Kept by hand while it lasts, so the browser's own anchoring can't add to it. */\nfunction setTopAnchor(el: HTMLElement, anchor: typeof topAnchor) {\n topAnchor = anchor\n el.style.overflowAnchor = anchor ? 'none' : ''\n}\n\nconst offsetOf = (node: Element, content: Element) =>\n node.getBoundingClientRect().top - content.getBoundingClientRect().top\n\nfunction anchorOf() {\n const content = contentRef.value\n const node = content?.firstElementChild\n return content && node ? { node, y: offsetOf(node, content) } : undefined\n}\n\nfunction keepTopAnchor() {\n const content = contentRef.value\n if (!content || !topAnchor?.node.isConnected) return\n const y = offsetOf(topAnchor.node, content)\n const shift = y - topAnchor.y\n if (Math.abs(shift) < 1) return\n topAnchor.y = y\n const el = viewportRef.value\n // a move iOS drops during a touch scroll is made once it is over\n if (el) touch.shiftScroll(el, shift)\n}\n\nfunction onWheel(event: WheelEvent) {\n // a wheel turn upwards stops following at once, even mid smooth-scroll\n if (event.deltaY < 0) following = false\n}\n\nfunction scrollToBottom(behavior: ScrollBehavior = reducedMotion.value ? 'auto' : 'smooth') {\n const el = viewportRef.value\n if (!el) return\n following = true\n el.scrollTo?.({ top: el.scrollHeight, behavior })\n if (!el.scrollTo) el.scrollTop = el.scrollHeight\n}\n\nfunction follow() {\n const el = viewportRef.value\n if (el && !props.items) keepTopAnchor()\n if (!el || !props.autoScroll || !following) {\n if (el) atBottom.value = distanceToBottom(el) <= props.threshold\n return\n }\n el.scrollTop = el.scrollHeight\n lastTop = el.scrollTop\n atBottom.value = true\n}\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n follow()\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver(follow)\n observeContent()\n if (viewportRef.value) observer.observe(viewportRef.value)\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n// A virtual list draws its content only once it has items.\nlet observed: HTMLElement | null | undefined\nfunction observeContent() {\n const content = contentRef.value\n if (!observer || content === observed) return\n if (observed) observer.unobserve(observed)\n observed = content\n if (content) observer.observe(content)\n}\nwatch(() => props.items?.length, observeContent, { flush: 'post' })\n\ndefineExpose<ChatListExpose>({\n get viewport() {\n return viewportRef.value\n },\n scrollToBottom,\n get atBottom() {\n return atBottom.value\n },\n})\n</script>\n\n<template>\n <div :class=\"[ns.b(), ns.is('at-bottom', atBottom), ns.is('virtual', !!items)]\">\n <DouVirtualList\n v-if=\"items\"\n ref=\"list\"\n :class=\"ns.e('viewport')\"\n :items=\"items\"\n :item-key=\"itemKey\"\n :estimate-size=\"estimateSize\"\n role=\"log\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <template #default=\"{ item, index }\">\n <slot name=\"item\" :item=\"item\" :index=\"index\" />\n </template>\n </DouVirtualList>\n <div\n v-else\n ref=\"viewport\"\n :class=\"ns.e('viewport')\"\n role=\"log\"\n tabindex=\"0\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <div ref=\"content\" :class=\"ns.e('content')\"><slot /></div>\n </div>\n <Transition name=\"dou-chat-list-button\">\n <div v-if=\"showScrollButton && !atBottom\" :class=\"ns.e('scroll')\">\n <slot name=\"button\" :scroll-to-bottom=\"() => scrollToBottom()\">\n <button\n type=\"button\"\n :class=\"ns.e('scroll-button')\"\n :aria-label=\"t('chat.scrollToBottom')\"\n @click=\"scrollToBottom()\"\n >\n <ArrowDownIcon />\n </button>\n </slot>\n </div>\n </Transition>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBA,MAAM,QAAQ;EAkBd,MAAM,OAAO;EAGb,MAAM,KAAK,aAAa,WAAW;EACnC,MAAM,gBAAgB,iBAAiB;EACvC,MAAM,EAAE,MAAM,UAAU;EAExB,MAAM,gBAAgB,eAA4B,UAAU;EAC5D,MAAM,eAAe,eAA4B,SAAS;EAC1D,MAAM,UAAU,eAAkC,MAAM;EAExD,MAAM,cAAc,EAClB,IAAI,QAAQ;GACV,OAAO,MAAM,QAAS,QAAQ,OAAO,GAAG,KAAK,OAAQ,cAAc;EACrE,EACF;EACA,MAAM,aAAa,EACjB,IAAI,QAAQ;GACV,OAAO,MAAM,QACR,YAAY,OAAO,oBACpB,aAAa;EACnB,EACF;EAEA,MAAM,WAAW,IAAI,IAAI;EACzB,MAAM,WAAW,UAAU,KAAK,kBAAkB,KAAK,CAAC;EAKxD,IAAI,YAAY;EAChB,IAAI,UAAU;EACd,IAAI,QAAQ;EACZ,MAAM,QAAQ,eAAe;EAE7B,SAAS,iBAAiB,IAAiB;GACzC,OAAO,GAAG,eAAe,GAAG,YAAY,GAAG;EAC7C;EAEA,MAAM,SAAS,OACb,iBAAiB,EAAE,CAAC,CAAC,OAAO,KAAK,GAAG,eAAe,GAAG;EAExD,SAAS,WAAW;GAClB,MAAM,KAAK,YAAY;GACvB,IAAI,CAAC,IAAI;GACT,MAAM,OAAO,iBAAiB,EAAE,KAAK,MAAM;GAC3C,IAAI,MAAM,YAAY;QACjB,IAAI,GAAG,YAAY,SAAS,YAAY;GAC7C,UAAU,GAAG;GACb,SAAS,QAAQ;GACjB,IAAI,CAAC,MAAM,EAAE,GAAG;IACd,QAAQ;IACR,aAAa,IAAI,KAAA,CAAS;GAC5B,OAAO,IAAI,CAAC,OAAO,MAAM,YAAY,QAAQ;EAC/C;EAKA,SAAS,WAAW;GAClB,MAAM,KAAK,YAAY;GACvB,IAAI,CAAC,MAAM,SAAS,CAAC,MAAM,EAAE,GAAG;GAChC,QAAQ;GAER,IAAI,CAAC,MAAM,OAAO,aAAa,IAAI,SAAS,CAAC;GAC7C,KAAK,YAAY;EACnB;;EAGA,IAAI;;EAEJ,SAAS,aAAa,IAAiB,QAA0B;GAC/D,YAAY;GACZ,GAAG,MAAM,iBAAiB,SAAS,SAAS;EAC9C;EAEA,MAAM,YAAY,MAAe,YAC/B,KAAK,sBAAsB,CAAC,CAAC,MAAM,QAAQ,sBAAsB,CAAC,CAAC;EAErE,SAAS,WAAW;GAClB,MAAM,UAAU,WAAW;GAC3B,MAAM,OAAO,SAAS;GACtB,OAAO,WAAW,OAAO;IAAE;IAAM,GAAG,SAAS,MAAM,OAAO;GAAE,IAAI,KAAA;EAClE;EAEA,SAAS,gBAAgB;GACvB,MAAM,UAAU,WAAW;GAC3B,IAAI,CAAC,WAAW,CAAC,WAAW,KAAK,aAAa;GAC9C,MAAM,IAAI,SAAS,UAAU,MAAM,OAAO;GAC1C,MAAM,QAAQ,IAAI,UAAU;GAC5B,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG;GACzB,UAAU,IAAI;GACd,MAAM,KAAK,YAAY;GAEvB,IAAI,IAAI,MAAM,YAAY,IAAI,KAAK;EACrC;EAEA,SAAS,QAAQ,OAAmB;GAElC,IAAI,MAAM,SAAS,GAAG,YAAY;EACpC;EAEA,SAAS,eAAe,WAA2B,cAAc,QAAQ,SAAS,UAAU;GAC1F,MAAM,KAAK,YAAY;GACvB,IAAI,CAAC,IAAI;GACT,YAAY;GACZ,GAAG,WAAW;IAAE,KAAK,GAAG;IAAc;GAAS,CAAC;GAChD,IAAI,CAAC,GAAG,UAAU,GAAG,YAAY,GAAG;EACtC;EAEA,SAAS,SAAS;GAChB,MAAM,KAAK,YAAY;GACvB,IAAI,MAAM,CAAC,MAAM,OAAO,cAAc;GACtC,IAAI,CAAC,MAAM,CAAC,MAAM,cAAc,CAAC,WAAW;IAC1C,IAAI,IAAI,SAAS,QAAQ,iBAAiB,EAAE,KAAK,MAAM;IACvD;GACF;GACA,GAAG,YAAY,GAAG;GAClB,UAAU,GAAG;GACb,SAAS,QAAQ;EACnB;EAEA,IAAI;EACJ,gBAAgB;GACd,OAAO;GACP,IAAI,OAAO,mBAAmB,aAAa;GAC3C,WAAW,IAAI,eAAe,MAAM;GACpC,eAAe;GACf,IAAI,YAAY,OAAO,SAAS,QAAQ,YAAY,KAAK;EAC3D,CAAC;EACD,sBAAsB,UAAU,WAAW,CAAC;EAG5C,IAAI;EACJ,SAAS,iBAAiB;GACxB,MAAM,UAAU,WAAW;GAC3B,IAAI,CAAC,YAAY,YAAY,UAAU;GACvC,IAAI,UAAU,SAAS,UAAU,QAAQ;GACzC,WAAW;GACX,IAAI,SAAS,SAAS,QAAQ,OAAO;EACvC;EACA,YAAY,MAAM,OAAO,QAAQ,gBAAgB,EAAE,OAAO,OAAO,CAAC;EAElE,SAA6B;GAC3B,IAAI,WAAW;IACb,OAAO,YAAY;GACrB;GACA;GACA,IAAI,WAAW;IACb,OAAO,SAAS;GAClB;EACF,CAAC;;GAIC,OAAAA,UAAA,GAAAC,mBA2CM,OAAA,EA3CA,OAAKC,eAAA;IAAGC,MAAA,EAAA,CAAE,CAAC,EAAC;IAAIA,MAAA,EAAA,CAAE,CAAC,GAAE,aAAc,SAAA,KAAQ;IAAGA,MAAA,EAAA,CAAE,CAAC,GAAE,WAAA,CAAA,CAAc,QAAA,KAAK;GAAA,CAAA,E,IAElE,QAAA,SADRH,UAAA,GAAAI,YAeiBD,MAAA,cAAA,GAAA;;IAbf,KAAI;IACH,OAAKD,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,UAAA,CAAA;IACX,OAAO,QAAA;IACP,YAAU,QAAA;IACV,iBAAe,QAAA;IAChB,MAAK;IACJ,cAAYA,MAAA,CAAA,CAAC,CAAA,eAAA;IACG,iBAAA;IACD,gBAAA;;IAEL,SAAOE,SACgC,EAD5B,MAAM,YAAK,CAC/BC,WAAgD,KAAA,QAAA,QAAA;KAAvB;KAAc;;;;;;;;;G,OAG3CN,UAAA,GAAAC,mBAWM,OAAA;;IATJ,KAAI;IACH,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,UAAA,CAAA;IACZ,MAAK;IACL,UAAS;IACR,cAAYA,MAAA,CAAA,CAAC,CAAA,eAAA;IACG,iBAAA;IACD,gBAAA;G,IAEhBI,mBAA0D,OAAA;IAArD,KAAI;IAAW,OAAKL,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,SAAA,CAAA;G,IAAaG,WAAQ,KAAA,QAAA,SAAA,C,0BAEtDE,YAaaC,YAAA,EAbD,MAAK,uBAAsB,GAAA;IACrC,SAAAJ,cAWM,CAXK,QAAA,oBAAgB,CAAK,SAAA,SAAhCL,UAAA,GAAAC,mBAWM,OAAA;;KAXqC,OAAKC,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,QAAA,CAAA;I,IACpDG,WASO,KAAA,QAAA,UAAA,EATc,sBAAwB,eAAc,EAA3D,SASO,CARLC,mBAOS,UAAA;KANP,MAAK;KACJ,OAAKL,eAAEC,MAAA,EAAA,CAAE,CAAC,EAAC,eAAA,CAAA;KACX,cAAYA,MAAA,CAAA,CAAC,CAAA,qBAAA;KACb,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,eAAc;I,IAEtBK,YAAiBL,MAAA,aAAA,CAAA,C"}
|
|
1
|
+
{"version":3,"file":"chat-list.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createBlock","_withCtx","_renderSlot","_createElementVNode","_createVNode","_Transition","_createCommentVNode"],"sources":["../../../src/components/chat-list/chat-list.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport type { HTMLAttributes } from 'vue'\nimport type { ForwardedAttributes } from '../../utils/install'\nimport { ArrowDownIcon } from '@dou-ui/icons'\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { DouVirtualList } from '../virtual-list'\nimport type { VirtualListAlign, VirtualListExpose } from '../virtual-list'\nimport { virtualItemKey } from '../virtual-list/virtual-list'\nimport { useReducedMotion } from '../../composables/use-motion'\nimport { useTouchScroll } from '../../composables/use-touch-scroll'\nimport { scrollPositionOf } from '../../utils/scroll'\nimport { useNamespace } from '../../utils/namespace'\nimport { devWarn } from '../../utils/status-check'\nimport { useLocale } from '../config-provider/use-config'\nimport type {\n ChatListEmits,\n ChatListExpose,\n ChatListProps,\n ChatListSlots,\n ChatListTarget,\n} from './chat-list'\nimport { elementOf, isTarget } from './chat-list'\n\ndefineOptions({ name: 'DouChatList' })\n\nconst props = withDefaults(\n defineProps<\n ChatListProps<T> &\n /* @vue-ignore */ ForwardedAttributes<\n HTMLAttributes,\n ChatListProps<T>,\n keyof ChatListEmits & string\n >\n >(),\n {\n autoScroll: true,\n threshold: 24,\n showScrollButton: true,\n items: undefined,\n itemKey: undefined,\n estimateSize: 120,\n },\n)\nconst emit = defineEmits<ChatListEmits>()\ndefineSlots<ChatListSlots<T>>()\n\nconst ns = useNamespace('chat-list')\nconst reducedMotion = useReducedMotion()\nconst { t } = useLocale()\n\nconst plainViewport = useTemplateRef<HTMLElement>('viewport')\nconst plainContent = useTemplateRef<HTMLElement>('content')\nconst listRef = useTemplateRef<VirtualListExpose>('list')\n// With `items`, the virtual list scrolls and its spacer holds the content.\nconst viewportRef = {\n get value() {\n return props.items ? (listRef.value?.el() ?? null) : plainViewport.value\n },\n}\nconst contentRef = {\n get value() {\n return props.items\n ? (viewportRef.value?.firstElementChild as HTMLElement | null | undefined)\n : plainContent.value\n },\n}\n\nconst atBottom = ref(true)\nwatch(atBottom, (value) => emit('atBottomChange', value))\n\n// Following is a decision of the reader: scrolling up turns it off,\n// reaching the bottom turns it on again. Growing content alone never\n// changes it.\nlet following = true\nlet lastTop = 0\nlet atTop = false\nconst touch = useTouchScroll()\n\nfunction distanceToBottom(el: HTMLElement) {\n return el.scrollHeight - el.scrollTop - el.clientHeight\n}\n\nconst isTop = (el: HTMLElement) =>\n scrollPositionOf(el).top <= 0 && el.scrollHeight > el.clientHeight\n\nfunction onScroll() {\n const el = viewportRef.value\n if (!el) return\n const near = distanceToBottom(el) <= props.threshold\n if (near) following = true\n else if (el.scrollTop < lastTop) following = false\n lastTop = el.scrollTop\n atBottom.value = near\n if (!isTop(el)) {\n atTop = false\n setTopAnchor(el, undefined)\n } else if (!atTop) touch.whenSettled(reachTop)\n}\n\n// Earlier messages loaded on top keep what is in view where it was. On iOS\n// the top is reached while the content still bounces; a position written\n// then is dropped, so `topReached` waits until the scroll is over.\nfunction reachTop() {\n const el = viewportRef.value\n if (!el || atTop || !isTop(el)) return\n atTop = true\n // the virtual list keeps its own first item in place\n if (!props.items) setTopAnchor(el, anchorOf())\n emit('topReached')\n}\n\n/** The first message, and how far it is from the top of the messages. */\nlet topAnchor: { node: Element; y: number } | undefined\n/** Kept by hand while it lasts, so the browser's own anchoring can't add to it. */\nfunction setTopAnchor(el: HTMLElement, anchor: typeof topAnchor) {\n topAnchor = anchor\n el.style.overflowAnchor = anchor ? 'none' : ''\n}\n\nconst offsetOf = (node: Element, content: Element) =>\n node.getBoundingClientRect().top - content.getBoundingClientRect().top\n\nfunction anchorOf() {\n const content = contentRef.value\n const node = content?.firstElementChild\n return content && node ? { node, y: offsetOf(node, content) } : undefined\n}\n\nfunction keepTopAnchor() {\n const content = contentRef.value\n if (!content || !topAnchor?.node.isConnected) return\n const y = offsetOf(topAnchor.node, content)\n const shift = y - topAnchor.y\n if (Math.abs(shift) < 1) return\n topAnchor.y = y\n const el = viewportRef.value\n // a move iOS drops during a touch scroll is made once it is over\n if (el) touch.shiftScroll(el, shift)\n}\n\nfunction onWheel(event: WheelEvent) {\n // a wheel turn upwards stops following at once, even mid smooth-scroll\n if (event.deltaY < 0) following = false\n}\n\nfunction scrollToBottom(behavior: ScrollBehavior = reducedMotion.value ? 'auto' : 'smooth') {\n const el = viewportRef.value\n if (!el) return\n following = true\n el.scrollTo?.({ top: el.scrollHeight, behavior })\n if (!el.scrollTo) el.scrollTop = el.scrollHeight\n}\n\nfunction follow() {\n const el = viewportRef.value\n if (el && !props.items) keepTopAnchor()\n if (!el || !props.autoScroll || !following) {\n if (el) atBottom.value = distanceToBottom(el) <= props.threshold\n return\n }\n el.scrollTop = el.scrollHeight\n lastTop = el.scrollTop\n atBottom.value = true\n}\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n follow()\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver(follow)\n observeContent()\n if (viewportRef.value) observer.observe(viewportRef.value)\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n// A virtual list draws its content only once it has items.\nlet observed: HTMLElement | null | undefined\nfunction observeContent() {\n const content = contentRef.value\n if (!observer || content === observed) return\n if (observed) observer.unobserve(observed)\n observed = content\n if (content) observer.observe(content)\n}\nwatch(() => props.items?.length, observeContent, { flush: 'post' })\n\n// Jump straight to the message, by key or by element alike: a virtual list\n// only knows where an item is once it is rendered, so it cannot glide there.\nfunction scrollToMessage(\n target: ChatListTarget | null | undefined,\n align: VirtualListAlign = 'center',\n) {\n // nothing there, e.g. the ref of a message no longer shown: like a key not found\n if (target == null) return\n if (isTarget(target)) {\n const el = viewportRef.value\n const node = elementOf(target)\n if (!el || !node || !el.contains(node)) return\n const box = el.getBoundingClientRect()\n const item = node.getBoundingClientRect()\n const top = item.top - box.top + el.scrollTop\n const offsets = { start: top, end: top + item.height - el.clientHeight }\n let to = top - (el.clientHeight - item.height) / 2\n if (align === 'start') to = offsets.start\n else if (align === 'end') to = offsets.end\n else if (align === 'nearest')\n to =\n item.top < box.top ? offsets.start : item.bottom > box.bottom ? offsets.end : el.scrollTop\n // the reader asked to look elsewhere: new content must not pull them away\n following = false\n el.scrollTop = Math.max(0, to)\n return\n }\n if (!props.items) {\n if (process.env.NODE_ENV !== 'production')\n devWarn(\n '[dou-ui] DouChatList: `scrollToMessage` takes a key only with `items`; with the default slot, pass the message’s element or component.',\n )\n return\n }\n const index = props.items.findIndex(\n (item, at) => virtualItemKey(props.itemKey, item, at) === target,\n )\n if (index < 0) return\n following = false\n listRef.value?.scrollToIndex(index, align)\n}\n\ndefineExpose<ChatListExpose>({\n get viewport() {\n return viewportRef.value\n },\n scrollToBottom,\n scrollToMessage,\n get atBottom() {\n return atBottom.value\n },\n})\n</script>\n\n<template>\n <div :class=\"[ns.b(), ns.is('at-bottom', atBottom), ns.is('virtual', !!items)]\">\n <DouVirtualList\n v-if=\"items\"\n ref=\"list\"\n :class=\"ns.e('viewport')\"\n :items=\"items\"\n :item-key=\"itemKey\"\n :estimate-size=\"estimateSize\"\n role=\"log\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <template #default=\"{ item, index }\">\n <slot name=\"item\" :item=\"item\" :index=\"index\" />\n </template>\n </DouVirtualList>\n <div\n v-else\n ref=\"viewport\"\n :class=\"ns.e('viewport')\"\n role=\"log\"\n tabindex=\"0\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <div ref=\"content\" :class=\"ns.e('content')\"><slot /></div>\n </div>\n <Transition name=\"dou-chat-list-button\">\n <div v-if=\"showScrollButton && !atBottom\" :class=\"ns.e('scroll')\">\n <slot name=\"button\" :scroll-to-bottom=\"() => scrollToBottom()\">\n <button\n type=\"button\"\n :class=\"ns.e('scroll-button')\"\n :aria-label=\"t('chat.scrollToBottom')\"\n @click=\"scrollToBottom()\"\n >\n <ArrowDownIcon />\n </button>\n </slot>\n </div>\n </Transition>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import chat_list_vue_vue_type_script_setup_true_lang_default from "./chat-list.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/components/chat-list/chat-list.vue
|
|
3
|
+
var chat_list_default = chat_list_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { chat_list_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=chat-list2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-list2.js","names":["_sfc_main"],"sources":["../../../src/components/chat-list/chat-list.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport type { HTMLAttributes } from 'vue'\nimport type { ForwardedAttributes } from '../../utils/install'\nimport { ArrowDownIcon } from '@dou-ui/icons'\nimport { onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { DouVirtualList } from '../virtual-list'\nimport type { VirtualListAlign, VirtualListExpose } from '../virtual-list'\nimport { virtualItemKey } from '../virtual-list/virtual-list'\nimport { useReducedMotion } from '../../composables/use-motion'\nimport { useTouchScroll } from '../../composables/use-touch-scroll'\nimport { scrollPositionOf } from '../../utils/scroll'\nimport { useNamespace } from '../../utils/namespace'\nimport { devWarn } from '../../utils/status-check'\nimport { useLocale } from '../config-provider/use-config'\nimport type {\n ChatListEmits,\n ChatListExpose,\n ChatListProps,\n ChatListSlots,\n ChatListTarget,\n} from './chat-list'\nimport { elementOf, isTarget } from './chat-list'\n\ndefineOptions({ name: 'DouChatList' })\n\nconst props = withDefaults(\n defineProps<\n ChatListProps<T> &\n /* @vue-ignore */ ForwardedAttributes<\n HTMLAttributes,\n ChatListProps<T>,\n keyof ChatListEmits & string\n >\n >(),\n {\n autoScroll: true,\n threshold: 24,\n showScrollButton: true,\n items: undefined,\n itemKey: undefined,\n estimateSize: 120,\n },\n)\nconst emit = defineEmits<ChatListEmits>()\ndefineSlots<ChatListSlots<T>>()\n\nconst ns = useNamespace('chat-list')\nconst reducedMotion = useReducedMotion()\nconst { t } = useLocale()\n\nconst plainViewport = useTemplateRef<HTMLElement>('viewport')\nconst plainContent = useTemplateRef<HTMLElement>('content')\nconst listRef = useTemplateRef<VirtualListExpose>('list')\n// With `items`, the virtual list scrolls and its spacer holds the content.\nconst viewportRef = {\n get value() {\n return props.items ? (listRef.value?.el() ?? null) : plainViewport.value\n },\n}\nconst contentRef = {\n get value() {\n return props.items\n ? (viewportRef.value?.firstElementChild as HTMLElement | null | undefined)\n : plainContent.value\n },\n}\n\nconst atBottom = ref(true)\nwatch(atBottom, (value) => emit('atBottomChange', value))\n\n// Following is a decision of the reader: scrolling up turns it off,\n// reaching the bottom turns it on again. Growing content alone never\n// changes it.\nlet following = true\nlet lastTop = 0\nlet atTop = false\nconst touch = useTouchScroll()\n\nfunction distanceToBottom(el: HTMLElement) {\n return el.scrollHeight - el.scrollTop - el.clientHeight\n}\n\nconst isTop = (el: HTMLElement) =>\n scrollPositionOf(el).top <= 0 && el.scrollHeight > el.clientHeight\n\nfunction onScroll() {\n const el = viewportRef.value\n if (!el) return\n const near = distanceToBottom(el) <= props.threshold\n if (near) following = true\n else if (el.scrollTop < lastTop) following = false\n lastTop = el.scrollTop\n atBottom.value = near\n if (!isTop(el)) {\n atTop = false\n setTopAnchor(el, undefined)\n } else if (!atTop) touch.whenSettled(reachTop)\n}\n\n// Earlier messages loaded on top keep what is in view where it was. On iOS\n// the top is reached while the content still bounces; a position written\n// then is dropped, so `topReached` waits until the scroll is over.\nfunction reachTop() {\n const el = viewportRef.value\n if (!el || atTop || !isTop(el)) return\n atTop = true\n // the virtual list keeps its own first item in place\n if (!props.items) setTopAnchor(el, anchorOf())\n emit('topReached')\n}\n\n/** The first message, and how far it is from the top of the messages. */\nlet topAnchor: { node: Element; y: number } | undefined\n/** Kept by hand while it lasts, so the browser's own anchoring can't add to it. */\nfunction setTopAnchor(el: HTMLElement, anchor: typeof topAnchor) {\n topAnchor = anchor\n el.style.overflowAnchor = anchor ? 'none' : ''\n}\n\nconst offsetOf = (node: Element, content: Element) =>\n node.getBoundingClientRect().top - content.getBoundingClientRect().top\n\nfunction anchorOf() {\n const content = contentRef.value\n const node = content?.firstElementChild\n return content && node ? { node, y: offsetOf(node, content) } : undefined\n}\n\nfunction keepTopAnchor() {\n const content = contentRef.value\n if (!content || !topAnchor?.node.isConnected) return\n const y = offsetOf(topAnchor.node, content)\n const shift = y - topAnchor.y\n if (Math.abs(shift) < 1) return\n topAnchor.y = y\n const el = viewportRef.value\n // a move iOS drops during a touch scroll is made once it is over\n if (el) touch.shiftScroll(el, shift)\n}\n\nfunction onWheel(event: WheelEvent) {\n // a wheel turn upwards stops following at once, even mid smooth-scroll\n if (event.deltaY < 0) following = false\n}\n\nfunction scrollToBottom(behavior: ScrollBehavior = reducedMotion.value ? 'auto' : 'smooth') {\n const el = viewportRef.value\n if (!el) return\n following = true\n el.scrollTo?.({ top: el.scrollHeight, behavior })\n if (!el.scrollTo) el.scrollTop = el.scrollHeight\n}\n\nfunction follow() {\n const el = viewportRef.value\n if (el && !props.items) keepTopAnchor()\n if (!el || !props.autoScroll || !following) {\n if (el) atBottom.value = distanceToBottom(el) <= props.threshold\n return\n }\n el.scrollTop = el.scrollHeight\n lastTop = el.scrollTop\n atBottom.value = true\n}\n\nlet observer: ResizeObserver | undefined\nonMounted(() => {\n follow()\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver(follow)\n observeContent()\n if (viewportRef.value) observer.observe(viewportRef.value)\n})\nonBeforeUnmount(() => observer?.disconnect())\n\n// A virtual list draws its content only once it has items.\nlet observed: HTMLElement | null | undefined\nfunction observeContent() {\n const content = contentRef.value\n if (!observer || content === observed) return\n if (observed) observer.unobserve(observed)\n observed = content\n if (content) observer.observe(content)\n}\nwatch(() => props.items?.length, observeContent, { flush: 'post' })\n\n// Jump straight to the message, by key or by element alike: a virtual list\n// only knows where an item is once it is rendered, so it cannot glide there.\nfunction scrollToMessage(\n target: ChatListTarget | null | undefined,\n align: VirtualListAlign = 'center',\n) {\n // nothing there, e.g. the ref of a message no longer shown: like a key not found\n if (target == null) return\n if (isTarget(target)) {\n const el = viewportRef.value\n const node = elementOf(target)\n if (!el || !node || !el.contains(node)) return\n const box = el.getBoundingClientRect()\n const item = node.getBoundingClientRect()\n const top = item.top - box.top + el.scrollTop\n const offsets = { start: top, end: top + item.height - el.clientHeight }\n let to = top - (el.clientHeight - item.height) / 2\n if (align === 'start') to = offsets.start\n else if (align === 'end') to = offsets.end\n else if (align === 'nearest')\n to =\n item.top < box.top ? offsets.start : item.bottom > box.bottom ? offsets.end : el.scrollTop\n // the reader asked to look elsewhere: new content must not pull them away\n following = false\n el.scrollTop = Math.max(0, to)\n return\n }\n if (!props.items) {\n if (process.env.NODE_ENV !== 'production')\n devWarn(\n '[dou-ui] DouChatList: `scrollToMessage` takes a key only with `items`; with the default slot, pass the message’s element or component.',\n )\n return\n }\n const index = props.items.findIndex(\n (item, at) => virtualItemKey(props.itemKey, item, at) === target,\n )\n if (index < 0) return\n following = false\n listRef.value?.scrollToIndex(index, align)\n}\n\ndefineExpose<ChatListExpose>({\n get viewport() {\n return viewportRef.value\n },\n scrollToBottom,\n scrollToMessage,\n get atBottom() {\n return atBottom.value\n },\n})\n</script>\n\n<template>\n <div :class=\"[ns.b(), ns.is('at-bottom', atBottom), ns.is('virtual', !!items)]\">\n <DouVirtualList\n v-if=\"items\"\n ref=\"list\"\n :class=\"ns.e('viewport')\"\n :items=\"items\"\n :item-key=\"itemKey\"\n :estimate-size=\"estimateSize\"\n role=\"log\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <template #default=\"{ item, index }\">\n <slot name=\"item\" :item=\"item\" :index=\"index\" />\n </template>\n </DouVirtualList>\n <div\n v-else\n ref=\"viewport\"\n :class=\"ns.e('viewport')\"\n role=\"log\"\n tabindex=\"0\"\n :aria-label=\"t('chat.messages')\"\n @scroll.passive=\"onScroll\"\n @wheel.passive=\"onWheel\"\n >\n <div ref=\"content\" :class=\"ns.e('content')\"><slot /></div>\n </div>\n <Transition name=\"dou-chat-list-button\">\n <div v-if=\"showScrollButton && !atBottom\" :class=\"ns.e('scroll')\">\n <slot name=\"button\" :scroll-to-bottom=\"() => scrollToBottom()\">\n <button\n type=\"button\"\n :class=\"ns.e('scroll-button')\"\n :aria-label=\"t('chat.scrollToBottom')\"\n @click=\"scrollToBottom()\"\n >\n <ArrowDownIcon />\n </button>\n </slot>\n </div>\n </Transition>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { withInstall } from "../../utils/install.js";
|
|
2
|
-
import chat_list_default from "./chat-
|
|
2
|
+
import chat_list_default from "./chat-list2.js";
|
|
3
3
|
// src/components/chat-list/index.ts
|
|
4
4
|
var DouChatList = withInstall(chat_list_default);
|
|
5
5
|
export { DouChatList, DouChatList as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-message.js","names":[],"sources":["../../../src/components/chat-message/chat-message.ts"],"sourcesContent":["import type { ChatArtifactKind, ChatArtifactStatus } from '../chat-artifact/chat-artifact'\nimport type { ChatAttachment } from '../chat-attachments/chat-attachments'\nimport type {\n ChatConfirmDecision,\n ChatConfirmStatus,\n ChatConfirmTone,\n} from '../chat-confirm/chat-confirm'\nimport type { ChatPlanItem } from '../chat-plan/chat-plan'\nimport type { ChatSource } from '../chat-sources/chat-sources'\nimport type { ChatStep } from '../chat-steps/chat-steps'\nimport type { ChatToolCallStatus } from '../chat-tool-call/chat-tool-call'\nimport type { MarkdownProps, MarkdownSlots } from '../markdown/markdown'\n\n/** Options for the Markdown of text and reasoning parts. */\nexport type ChatMessageMarkdown = Pick<\n MarkdownProps,\n 'smooth' | 'speed' | 'caret' | 'linkTarget' | 'sanitizeUrl'\n>\n\n/** Who wrote the message. */\nexport type ChatMessageSender = 'user' | 'assistant' | 'system'\n\nexport interface ChatTextPart {\n type: 'text'\n text: string\n /** More text is still coming. */\n streaming?: boolean\n}\nexport interface ChatReasoningPart {\n type: 'reasoning'\n text: string\n thinking?: boolean\n /** Seconds it took. */\n duration?: number\n}\nexport interface ChatToolPart {\n type: 'tool'\n name?: string\n title?: string\n status?: ChatToolCallStatus\n input?: unknown\n output?: unknown\n live?: boolean\n}\nexport interface ChatStepsPart {\n type: 'steps'\n items: ChatStep[]\n live?: boolean\n title?: string\n}\nexport interface ChatPlanPart {\n type: 'plan'\n items: ChatPlanItem[]\n title?: string\n collapsible?: boolean\n live?: boolean\n}\nexport interface ChatSourcesPart {\n type: 'sources'\n items: ChatSource[]\n}\nexport interface ChatFilesPart {\n type: 'files'\n files: ChatAttachment[]\n}\nexport interface ChatArtifactPart {\n type: 'artifact'\n /** Handed back with `artifact`, to tell artifacts apart. */\n id?: string | number\n title: string\n kind?: ChatArtifactKind\n description?: string\n status?: ChatArtifactStatus\n active?: boolean\n}\nexport interface ChatConfirmPart {\n type: 'confirm'\n /** Handed back with `confirm`, to tell requests apart. */\n id?: string | number\n title: string\n description?: string\n status?: ChatConfirmStatus\n allowAlways?: boolean\n tone?: ChatConfirmTone\n /** Shown as monospace detail, e.g. the command to run or a diff. */\n detail?: string\n}\n/** Any other part; render it with the `part` slot. */\nexport interface ChatCustomPart {\n type: string\n [key: string]: unknown\n}\n\nexport type ChatMessagePart =\n | ChatTextPart\n | ChatReasoningPart\n | ChatToolPart\n | ChatStepsPart\n | ChatPlanPart\n | ChatSourcesPart\n | ChatFilesPart\n | ChatArtifactPart\n | ChatConfirmPart\n\nexport interface ChatMessageProps {\n sender?: ChatMessageSender\n /**\n * What the message is made of, in order: text, reasoning, tool calls,\n * steps, a plan, sources, files, artifacts and
|
|
1
|
+
{"version":3,"file":"chat-message.js","names":[],"sources":["../../../src/components/chat-message/chat-message.ts"],"sourcesContent":["import type { ChatArtifactKind, ChatArtifactStatus } from '../chat-artifact/chat-artifact'\nimport type { ChatAttachment } from '../chat-attachments/chat-attachments'\nimport type {\n ChatConfirmDecision,\n ChatConfirmStatus,\n ChatConfirmTone,\n} from '../chat-confirm/chat-confirm'\nimport type { ChatPlanItem } from '../chat-plan/chat-plan'\nimport type { ChatQuestionOption, ChatQuestionStatus } from '../chat-question/chat-question'\nimport type { ChatSource } from '../chat-sources/chat-sources'\nimport type { ChatStep } from '../chat-steps/chat-steps'\nimport type { ChatToolCallStatus } from '../chat-tool-call/chat-tool-call'\nimport type { MarkdownProps, MarkdownSlots } from '../markdown/markdown'\n\n/** Options for the Markdown of text and reasoning parts. */\nexport type ChatMessageMarkdown = Pick<\n MarkdownProps,\n 'smooth' | 'speed' | 'caret' | 'linkTarget' | 'sanitizeUrl'\n>\n\n/** Who wrote the message. */\nexport type ChatMessageSender = 'user' | 'assistant' | 'system'\n\n/** When the actions under a message show. */\nexport type ChatMessageActionsVisible = 'always' | 'hover'\n\nexport interface ChatTextPart {\n type: 'text'\n text: string\n /** More text is still coming. */\n streaming?: boolean\n}\nexport interface ChatReasoningPart {\n type: 'reasoning'\n text: string\n thinking?: boolean\n /** Seconds it took. */\n duration?: number\n}\nexport interface ChatToolPart {\n type: 'tool'\n name?: string\n title?: string\n status?: ChatToolCallStatus\n input?: unknown\n output?: unknown\n live?: boolean\n}\nexport interface ChatStepsPart {\n type: 'steps'\n items: ChatStep[]\n live?: boolean\n title?: string\n}\nexport interface ChatPlanPart {\n type: 'plan'\n items: ChatPlanItem[]\n title?: string\n collapsible?: boolean\n live?: boolean\n}\nexport interface ChatSourcesPart {\n type: 'sources'\n items: ChatSource[]\n}\nexport interface ChatFilesPart {\n type: 'files'\n files: ChatAttachment[]\n}\nexport interface ChatArtifactPart {\n type: 'artifact'\n /** Handed back with `artifact`, to tell artifacts apart. */\n id?: string | number\n title: string\n kind?: ChatArtifactKind\n description?: string\n status?: ChatArtifactStatus\n active?: boolean\n}\nexport interface ChatConfirmPart {\n type: 'confirm'\n /** Handed back with `confirm`, to tell requests apart. */\n id?: string | number\n title: string\n description?: string\n status?: ChatConfirmStatus\n allowAlways?: boolean\n tone?: ChatConfirmTone\n /** Shown as monospace detail, e.g. the command to run or a diff. */\n detail?: string\n}\nexport interface ChatQuestionPart {\n type: 'question'\n /** Handed back with `question`, to tell questions apart. */\n id?: string | number\n title: string\n description?: string\n options: ChatQuestionOption[]\n status?: ChatQuestionStatus\n /** The `value` of the option chosen. */\n answer?: string | number\n}\n/** Any other part; render it with the `part` slot. */\nexport interface ChatCustomPart {\n type: string\n [key: string]: unknown\n}\n\nexport type ChatMessagePart =\n | ChatTextPart\n | ChatReasoningPart\n | ChatToolPart\n | ChatStepsPart\n | ChatPlanPart\n | ChatSourcesPart\n | ChatFilesPart\n | ChatArtifactPart\n | ChatConfirmPart\n | ChatQuestionPart\n\nexport interface ChatMessageProps {\n sender?: ChatMessageSender\n /**\n * What the message is made of, in order: text, reasoning, tool calls,\n * steps, a plan, sources, files, artifacts, confirmations and questions may follow\n * one another in any order.\n */\n parts?: (ChatMessagePart | ChatCustomPart)[]\n /** Shorthand for a message of one text part. */\n content?: string\n /**\n * Render text and reasoning parts as Markdown, streaming included. `[1]`\n * in the text becomes a citation of the message's sources parts. An\n * object turns it on with options for the Markdown component, such as\n * `sanitizeUrl`. Set it for every message in the ConfigProvider's\n * `defaults`.\n */\n markdown?: boolean | ChatMessageMarkdown\n /** No part has arrived yet: show the waiting dots, like ChatBubble's. */\n loading?: boolean\n /**\n * When the actions show: `'always'`, or `'hover'` while the pointer is\n * over the message or focus is inside. Devices without hover always show\n * them. Buttons from the `actions` slot follow along. Defaults to\n * `'hover'` for the person's messages and `'always'` for the rest.\n */\n actionsVisible?: ChatMessageActionsVisible\n /** Show a copy button among the actions, copying the text parts. */\n copyable?: boolean\n /** Offer an edit button on the person's own messages; saving emits `edit`. */\n editable?: boolean\n /** How many versions of this message there are, e.g. after regenerating. Two or more show a switcher. */\n versions?: number\n /** Which version is shown, from 1. Bound with `v-model:version`. */\n version?: number\n /**\n * Mark this message with a ring in the primary color that hugs the\n * bubble, e.g. after a search jumped to it. It stays while set and fades\n * once turned off (at once with reduced motion): turn it off after a\n * moment. Pair it with ChatList's `scrollToMessage`.\n */\n highlighted?: boolean\n}\n\nexport interface ChatMessageEmits {\n 'update:version': [version: number]\n /** An edited message was saved, with its new text. */\n edit: [text: string]\n /** A confirmation part was answered. */\n confirm: [part: ChatConfirmPart, decision: ChatConfirmDecision]\n /** An artifact part was pressed. */\n artifact: [part: ChatArtifactPart]\n /** An option of a question part was pressed, with its `value` and the option itself. */\n question: [part: ChatQuestionPart, value: string | number, option: ChatQuestionOption]\n}\n\nexport interface ChatMessageSlots {\n /**\n * Renders a text part by hand, in place of plain text or `markdown`.\n * `text` is what has been revealed so far; `part.text` is all of it.\n * `sources` are the message's sources, for citations.\n */\n text?: (props: {\n part: ChatTextPart\n text: string\n index: number\n sources: ChatSource[]\n }) => unknown\n /** Renders parts of a type the message does not know. */\n part?: (props: { part: ChatCustomPart; index: number }) => unknown\n /** Buttons after the built-in actions, e.g. regenerate or feedback. */\n actions?: () => unknown\n /** Replaces all parts, for a message laid out by hand. */\n default?: () => unknown\n}\n\n/**\n * The Markdown component's slots, with a `markdown-` prefix, for text\n * rendered with `markdown`: `markdown-code`, `markdown-container-tip`, ...\n */\nexport type ChatMessageMarkdownSlots = {\n [K in keyof MarkdownSlots as `markdown-${K & string}`]: MarkdownSlots[K]\n}\n\n/** The text of a message, for copying: its text parts, one paragraph each. */\nexport function messageText(parts: readonly { type: string; text?: unknown }[]): string {\n return parts\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text as string)\n .join('\\n\\n')\n}\n"],"mappings":";;AA6MA,SAAgB,YAAY,OAA4D;CACtF,OAAO,MACJ,QAAQ,SAAS,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS,QAAQ,CAAC,CACvE,KAAK,SAAS,KAAK,IAAc,CAAC,CAClC,KAAK,MAAM;AAChB"}
|
|
@@ -6,6 +6,7 @@ import { DouChatArtifact } from "../chat-artifact/index.js";
|
|
|
6
6
|
import { DouChatAttachments } from "../chat-attachments/index.js";
|
|
7
7
|
import { DouChatConfirm } from "../chat-confirm/index.js";
|
|
8
8
|
import { DouTooltip } from "../tooltip/index.js";
|
|
9
|
+
import { DouChatQuestion } from "../chat-question/index.js";
|
|
9
10
|
import { planRunning } from "../chat-plan/chat-plan.js";
|
|
10
11
|
import { DouChatPlan } from "../chat-plan/index.js";
|
|
11
12
|
import { DouChatReasoning } from "../chat-reasoning/index.js";
|
|
@@ -20,7 +21,7 @@ import { messageText } from "./chat-message.js";
|
|
|
20
21
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, mergeProps, nextTick, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useSlots, useTemplateRef, vModelText, withCtx, withDirectives } from "vue";
|
|
21
22
|
import { ChevronLeftIcon, ChevronRightIcon, EditIcon } from "@dou-ui/icons";
|
|
22
23
|
// src/components/chat-message/chat-message.vue?vue&type=script&setup=true&lang.ts
|
|
23
|
-
var _hoisted_1 = ["aria-busy"];
|
|
24
|
+
var _hoisted_1 = ["aria-busy", "aria-current"];
|
|
24
25
|
var _hoisted_2 = ["aria-label"];
|
|
25
26
|
var _hoisted_3 = ["aria-label"];
|
|
26
27
|
var _hoisted_4 = ["aria-label"];
|
|
@@ -40,6 +41,7 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
40
41
|
type: Boolean,
|
|
41
42
|
default: false
|
|
42
43
|
},
|
|
44
|
+
actionsVisible: { default: void 0 },
|
|
43
45
|
copyable: {
|
|
44
46
|
type: Boolean,
|
|
45
47
|
default: true
|
|
@@ -49,13 +51,15 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
49
51
|
default: false
|
|
50
52
|
},
|
|
51
53
|
versions: { default: 1 },
|
|
52
|
-
version: { default: void 0 }
|
|
54
|
+
version: { default: void 0 },
|
|
55
|
+
highlighted: { type: Boolean }
|
|
53
56
|
},
|
|
54
57
|
emits: [
|
|
55
58
|
"update:version",
|
|
56
59
|
"edit",
|
|
57
60
|
"confirm",
|
|
58
|
-
"artifact"
|
|
61
|
+
"artifact",
|
|
62
|
+
"question"
|
|
59
63
|
],
|
|
60
64
|
setup(__props, { emit: __emit }) {
|
|
61
65
|
const props = __props;
|
|
@@ -73,6 +77,7 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
73
77
|
inner: name.slice(9)
|
|
74
78
|
}));
|
|
75
79
|
const isUser = computed(() => props.sender === "user");
|
|
80
|
+
const actionsShow = computed(() => props.actionsVisible ?? (isUser.value ? "hover" : "always"));
|
|
76
81
|
const files = computed(() => isUser.value ? parts.value.filter((p) => p.type === "files") : []);
|
|
77
82
|
const body = computed(() => isUser.value ? parts.value.filter((p) => p.type !== "files") : parts.value);
|
|
78
83
|
const text = computed(() => messageText(parts.value));
|
|
@@ -139,6 +144,7 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
139
144
|
const asFiles = (part) => part;
|
|
140
145
|
const asConfirm = (part) => part;
|
|
141
146
|
const asArtifact = (part) => part;
|
|
147
|
+
const asQuestion = (part) => part;
|
|
142
148
|
const known = /* @__PURE__ */ new Set([
|
|
143
149
|
"text",
|
|
144
150
|
"reasoning",
|
|
@@ -148,7 +154,8 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
148
154
|
"sources",
|
|
149
155
|
"files",
|
|
150
156
|
"artifact",
|
|
151
|
-
"confirm"
|
|
157
|
+
"confirm",
|
|
158
|
+
"question"
|
|
152
159
|
]);
|
|
153
160
|
const rest = (part) => {
|
|
154
161
|
const { type: _type, ...others } = part;
|
|
@@ -159,10 +166,13 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
159
166
|
class: normalizeClass([
|
|
160
167
|
unref(ns).b(),
|
|
161
168
|
unref(ns).m(__props.sender),
|
|
169
|
+
unref(ns).m(`actions-${actionsShow.value}`),
|
|
162
170
|
unref(ns).is("loading", __props.loading),
|
|
163
|
-
unref(ns).is("editing", editing.value)
|
|
171
|
+
unref(ns).is("editing", editing.value),
|
|
172
|
+
unref(ns).is("highlighted", __props.highlighted)
|
|
164
173
|
]),
|
|
165
|
-
"aria-busy": busy.value || void 0
|
|
174
|
+
"aria-busy": busy.value || void 0,
|
|
175
|
+
"aria-current": __props.highlighted || void 0
|
|
166
176
|
}, [
|
|
167
177
|
files.value.length && !editing.value ? (openBlock(), createElementBlock("div", {
|
|
168
178
|
key: 0,
|
|
@@ -303,10 +313,25 @@ var chat_message_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
303
313
|
"allow-always",
|
|
304
314
|
"tone",
|
|
305
315
|
"onDecide"
|
|
316
|
+
])) : part.type === "question" ? (openBlock(), createBlock(unref(DouChatQuestion), {
|
|
317
|
+
key: 10,
|
|
318
|
+
title: asQuestion(part).title,
|
|
319
|
+
description: asQuestion(part).description,
|
|
320
|
+
options: asQuestion(part).options,
|
|
321
|
+
status: asQuestion(part).status,
|
|
322
|
+
answer: asQuestion(part).answer,
|
|
323
|
+
onSelect: (value, option) => emit("question", asQuestion(part), value, option)
|
|
324
|
+
}, null, 8, [
|
|
325
|
+
"title",
|
|
326
|
+
"description",
|
|
327
|
+
"options",
|
|
328
|
+
"status",
|
|
329
|
+
"answer",
|
|
330
|
+
"onSelect"
|
|
306
331
|
])) : !unref(known).has(part.type) ? renderSlot(_ctx.$slots, "part", {
|
|
307
332
|
part,
|
|
308
333
|
index
|
|
309
|
-
}, void 0, void 0,
|
|
334
|
+
}, void 0, void 0, 11) : createCommentVNode("", true)], 64);
|
|
310
335
|
}), 128))]), __props.loading ? (openBlock(), createElementBlock("span", {
|
|
311
336
|
key: 0,
|
|
312
337
|
class: normalizeClass(unref(ns).e("loading")),
|
package/dist/components/chat-message/chat-message.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-message.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createBlock","_createCommentVNode","_withDirectives","_createElementVNode","_vModelText","_createVNode","_withCtx","_createTextVNode","_toDisplayString","_renderSlot","_mergeProps","_createSlots"],"sources":["../../../src/components/chat-message/chat-message.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ChevronLeftIcon, ChevronRightIcon, EditIcon } from '@dou-ui/icons'\nimport { computed, nextTick, ref, useTemplateRef } from 'vue'\nimport { isComposing } from '../../utils/keyboard'\nimport { useNamespace } from '../../utils/namespace'\nimport { DouButton } from '../button'\nimport { DouChatArtifact } from '../chat-artifact'\nimport { DouChatAttachments } from '../chat-attachments'\nimport { DouChatConfirm } from '../chat-confirm'\nimport { DouChatPlan } from '../chat-plan'\nimport { planRunning, type ChatPlanItem } from '../chat-plan/chat-plan'\nimport { DouChatReasoning } from '../chat-reasoning'\nimport { DouChatSources } from '../chat-sources'\nimport { DouChatSteps } from '../chat-steps'\nimport { stepsLive, type ChatStep } from '../chat-steps/chat-steps'\nimport { DouChatToolCall } from '../chat-tool-call'\nimport { useLocale } from '../config-provider/use-config'\nimport { DouCopyButton } from '../copy-button'\nimport { DouMarkdown } from '../markdown'\nimport type { ChatSource } from '../chat-sources/chat-sources'\nimport { DouStreamText } from '../stream-text'\nimport { DouTooltip } from '../tooltip'\nimport { messageText } from './chat-message'\nimport type {\n ChatArtifactPart,\n ChatConfirmPart,\n ChatCustomPart,\n ChatMessageEmits,\n ChatMessagePart,\n ChatMessageMarkdownSlots,\n ChatMessageProps,\n ChatMessageSlots,\n} from './chat-message'\n\ndefineOptions({ name: 'DouChatMessage' })\n\nconst props = withDefaults(defineProps<ChatMessageProps>(), {\n sender: 'assistant',\n loading: false,\n copyable: true,\n editable: false,\n markdown: false,\n versions: 1,\n version: undefined,\n})\nconst emit = defineEmits<ChatMessageEmits>()\nconst slots = defineSlots<ChatMessageSlots & ChatMessageMarkdownSlots>()\n\nconst ns = useNamespace('chat-message')\nconst { t } = useLocale()\n\ntype AnyPart = ChatMessagePart | ChatCustomPart\n\nconst parts = computed<AnyPart[]>(\n () => props.parts ?? (props.content === undefined ? [] : [{ type: 'text', text: props.content }]),\n)\nconst markdownOptions = computed(() =>\n typeof props.markdown === 'object' ? props.markdown : undefined,\n)\n// `markdown-code` here is the Markdown component's `code`\nconst PREFIX = 'markdown-'\nconst markdownSlots = () =>\n Object.keys(slots)\n .filter((name) => name.startsWith(PREFIX))\n .map((name) => ({\n name: name as keyof ChatMessageMarkdownSlots,\n inner: name.slice(PREFIX.length),\n }))\nconst isUser = computed(() => props.sender === 'user')\n// the person's attachments sit above their bubble, not inside it\nconst files = computed(() => (isUser.value ? parts.value.filter((p) => p.type === 'files') : []))\nconst body = computed(() =>\n isUser.value ? parts.value.filter((p) => p.type !== 'files') : parts.value,\n)\nconst text = computed(() => messageText(parts.value as { type: string; text?: unknown }[]))\n// Every source of the message, numbered in order, for `[1]` in Markdown\n// text. The same array while the sources stay the same, so text that\n// streams in does not redraw the whole reply.\nlet lastSources: ChatSource[] = []\nconst sources = computed(() => {\n const next = parts.value.flatMap((part) =>\n part.type === 'sources' ? ((part as { items?: ChatSource[] }).items ?? []) : [],\n )\n if (next.length === lastSources.length && next.every((s, i) => s === lastSources[i]))\n return lastSources\n return (lastSources = next)\n})\n\nconst busy = computed(\n () =>\n props.loading ||\n parts.value.some((part) => {\n const p = part as Record<string, unknown>\n // an artifact can keep being made after the reply ends, so it doesn't hold the actions back\n const working = p.status === 'running' && p.type !== 'artifact' && p.live !== false\n // busy exactly when steps or a plan show themselves running\n const live = p.live as boolean | undefined\n const steps = p.type === 'steps' && stepsLive(p.items as ChatStep[] | undefined, live)\n const plan = p.type === 'plan' && planRunning(p.items as ChatPlanItem[] | undefined, live)\n return !!p.streaming || !!p.thinking || steps || plan || working\n }),\n)\n\nconst innerVersion = ref(props.versions)\nconst version = computed(() => Math.min(props.version ?? innerVersion.value, props.versions))\nfunction step(delta: number) {\n const next = Math.min(Math.max(version.value + delta, 1), props.versions)\n innerVersion.value = next\n emit('update:version', next)\n}\n\nconst showActions = computed(\n () =>\n !busy.value &&\n (props.versions > 1 ||\n (props.copyable && !!text.value) ||\n (props.editable && isUser.value) ||\n !!slots.actions),\n)\n\n// editing in place\nconst editing = ref(false)\nconst draft = ref('')\nconst editorRef = useTemplateRef<HTMLTextAreaElement>('editor')\nfunction startEdit() {\n draft.value = text.value\n editing.value = true\n void nextTick(() => {\n const el = editorRef.value\n el?.focus()\n el?.setSelectionRange(el.value.length, el.value.length)\n fitEditor()\n })\n}\nfunction cancelEdit() {\n editing.value = false\n}\nfunction saveEdit() {\n if (!draft.value.trim()) return\n editing.value = false\n emit('edit', draft.value)\n}\nfunction fitEditor() {\n const el = editorRef.value\n if (!el) return\n el.style.height = 'auto'\n el.style.height = `${el.scrollHeight}px`\n}\nfunction onEditorKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEdit()\n } else if (event.key === 'Enter' && !event.shiftKey && !isComposing(event)) {\n event.preventDefault()\n saveEdit()\n }\n}\n\nconst asText = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'text' }>\nconst asReasoning = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'reasoning' }>\nconst asFiles = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'files' }>\nconst asConfirm = (part: AnyPart) => part as ChatConfirmPart\nconst asArtifact = (part: AnyPart) => part as ChatArtifactPart\nconst known = new Set([\n 'text',\n 'reasoning',\n 'tool',\n 'steps',\n 'plan',\n 'sources',\n 'files',\n 'artifact',\n 'confirm',\n])\n// props for the part components, without the `type` discriminator\nconst rest = (part: AnyPart) => {\n const { type: _type, ...others } = part as Record<string, unknown>\n return others\n}\n</script>\n\n<template>\n <div\n :class=\"[ns.b(), ns.m(sender), ns.is('loading', loading), ns.is('editing', editing)]\"\n :aria-busy=\"busy || undefined\"\n >\n <div v-if=\"files.length && !editing\" :class=\"ns.e('files')\">\n <template v-for=\"(part, index) in files\" :key=\"`f${index}`\">\n <DouChatAttachments :model-value=\"asFiles(part).files\" :removable=\"false\" />\n </template>\n </div>\n\n <div v-if=\"editing\" :class=\"ns.e('editor')\">\n <textarea\n ref=\"editor\"\n v-model=\"draft\"\n :class=\"ns.e('editor-native')\"\n rows=\"1\"\n :aria-label=\"t('chat.edit')\"\n @input=\"fitEditor\"\n @keydown=\"onEditorKeydown\"\n />\n <div :class=\"ns.e('editor-actions')\">\n <DouButton size=\"sm\" variant=\"ghost\" @click=\"cancelEdit\">{{ t('chat.cancel') }}</DouButton>\n <DouButton size=\"sm\" :disabled=\"!draft.trim()\" @click=\"saveEdit\">\n {{ t('chat.send') }}\n </DouButton>\n </div>\n </div>\n\n <div v-else-if=\"body.length || loading || slots.default\" :class=\"ns.e('content')\">\n <slot>\n <template v-for=\"(part, index) in body\" :key=\"index\">\n <DouMarkdown\n v-if=\"part.type === 'text' && markdown && !slots.text\"\n v-bind=\"markdownOptions\"\n :class=\"ns.e('text')\"\n :content=\"asText(part).text\"\n :streaming=\"!!asText(part).streaming\"\n :sources=\"sources.length ? sources : undefined\"\n >\n <template v-for=\"slot in markdownSlots()\" :key=\"slot.name\" #[slot.inner]=\"scope\">\n <slot :name=\"slot.name\" v-bind=\"scope\" />\n </template>\n </DouMarkdown>\n <DouStreamText\n v-else-if=\"part.type === 'text'\"\n :class=\"ns.e('text')\"\n :content=\"asText(part).text\"\n :streaming=\"!!asText(part).streaming\"\n >\n <template v-if=\"slots.text\" #default=\"{ text: shown }\">\n <slot\n name=\"text\"\n :part=\"asText(part)\"\n :text=\"shown\"\n :index=\"index\"\n :sources=\"sources\"\n />\n </template>\n </DouStreamText>\n <DouChatReasoning\n v-else-if=\"part.type === 'reasoning'\"\n :thinking=\"!!asReasoning(part).thinking\"\n :duration=\"asReasoning(part).duration\"\n >\n <DouMarkdown\n v-if=\"markdown\"\n v-bind=\"markdownOptions\"\n :content=\"asReasoning(part).text\"\n :streaming=\"!!asReasoning(part).thinking\"\n :caret=\"false\"\n >\n <template v-for=\"slot in markdownSlots()\" :key=\"slot.name\" #[slot.inner]=\"scope\">\n <slot :name=\"slot.name\" v-bind=\"scope\" />\n </template>\n </DouMarkdown>\n <DouStreamText\n v-else\n :content=\"asReasoning(part).text\"\n :streaming=\"!!asReasoning(part).thinking\"\n :caret=\"false\"\n />\n </DouChatReasoning>\n <DouChatToolCall v-else-if=\"part.type === 'tool'\" v-bind=\"rest(part)\" />\n <DouChatSteps v-else-if=\"part.type === 'steps'\" v-bind=\"rest(part)\" />\n <DouChatPlan v-else-if=\"part.type === 'plan'\" v-bind=\"rest(part)\" />\n <DouChatSources v-else-if=\"part.type === 'sources'\" v-bind=\"rest(part)\" />\n <DouChatAttachments\n v-else-if=\"part.type === 'files'\"\n :model-value=\"asFiles(part).files\"\n :removable=\"false\"\n />\n <DouChatArtifact\n v-else-if=\"part.type === 'artifact'\"\n v-bind=\"rest(part)\"\n @open=\"emit('artifact', asArtifact(part))\"\n />\n <DouChatConfirm\n v-else-if=\"part.type === 'confirm'\"\n :title=\"asConfirm(part).title\"\n :description=\"asConfirm(part).description\"\n :status=\"asConfirm(part).status\"\n :allow-always=\"asConfirm(part).allowAlways\"\n :tone=\"asConfirm(part).tone\"\n @decide=\"emit('confirm', asConfirm(part), $event)\"\n >\n <pre v-if=\"asConfirm(part).detail\" :class=\"ns.e('code')\">{{\n asConfirm(part).detail\n }}</pre>\n </DouChatConfirm>\n <slot\n v-else-if=\"!known.has(part.type)\"\n name=\"part\"\n :part=\"part as ChatCustomPart\"\n :index=\"index\"\n />\n </template>\n </slot>\n <span v-if=\"loading\" :class=\"ns.e('loading')\" role=\"status\" :aria-label=\"t('chat.loading')\">\n <i /><i /><i />\n </span>\n </div>\n\n <div v-if=\"showActions && !editing\" :class=\"ns.e('actions')\">\n <div\n v-if=\"versions > 1\"\n :class=\"ns.e('versions')\"\n role=\"group\"\n :aria-label=\"t('chat.versions')\"\n >\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"version <= 1\"\n :aria-label=\"t('chat.previousVersion')\"\n @click=\"step(-1)\"\n >\n <ChevronLeftIcon />\n </DouButton>\n <span :class=\"ns.e('version')\">{{ version }} / {{ versions }}</span>\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"version >= versions\"\n :aria-label=\"t('chat.nextVersion')\"\n @click=\"step(1)\"\n >\n <ChevronRightIcon />\n </DouButton>\n </div>\n <DouCopyButton v-if=\"copyable && text\" size=\"sm\" :text=\"text\" />\n <DouTooltip v-if=\"editable && isUser\" :content=\"t('chat.edit')\">\n <DouButton variant=\"ghost\" size=\"sm\" :aria-label=\"t('chat.edit')\" @click=\"startEdit\">\n <EditIcon />\n </DouButton>\n </DouTooltip>\n <slot name=\"actions\" />\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat-message.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createBlock","_createCommentVNode","_withDirectives","_createElementVNode","_vModelText","_createVNode","_withCtx","_createTextVNode","_toDisplayString","_renderSlot","_mergeProps","_createSlots"],"sources":["../../../src/components/chat-message/chat-message.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ChevronLeftIcon, ChevronRightIcon, EditIcon } from '@dou-ui/icons'\nimport { computed, nextTick, ref, useTemplateRef } from 'vue'\nimport { isComposing } from '../../utils/keyboard'\nimport { useNamespace } from '../../utils/namespace'\nimport { DouButton } from '../button'\nimport { DouChatArtifact } from '../chat-artifact'\nimport { DouChatAttachments } from '../chat-attachments'\nimport { DouChatConfirm } from '../chat-confirm'\nimport { DouChatQuestion } from '../chat-question'\nimport { DouChatPlan } from '../chat-plan'\nimport { planRunning, type ChatPlanItem } from '../chat-plan/chat-plan'\nimport { DouChatReasoning } from '../chat-reasoning'\nimport { DouChatSources } from '../chat-sources'\nimport { DouChatSteps } from '../chat-steps'\nimport { stepsLive, type ChatStep } from '../chat-steps/chat-steps'\nimport { DouChatToolCall } from '../chat-tool-call'\nimport { useLocale } from '../config-provider/use-config'\nimport { DouCopyButton } from '../copy-button'\nimport { DouMarkdown } from '../markdown'\nimport type { ChatSource } from '../chat-sources/chat-sources'\nimport { DouStreamText } from '../stream-text'\nimport { DouTooltip } from '../tooltip'\nimport { messageText } from './chat-message'\nimport type {\n ChatArtifactPart,\n ChatConfirmPart,\n ChatQuestionPart,\n ChatCustomPart,\n ChatMessageEmits,\n ChatMessagePart,\n ChatMessageMarkdownSlots,\n ChatMessageProps,\n ChatMessageSlots,\n} from './chat-message'\n\ndefineOptions({ name: 'DouChatMessage' })\n\nconst props = withDefaults(defineProps<ChatMessageProps>(), {\n sender: 'assistant',\n loading: false,\n copyable: true,\n editable: false,\n markdown: false,\n actionsVisible: undefined,\n versions: 1,\n version: undefined,\n})\nconst emit = defineEmits<ChatMessageEmits>()\nconst slots = defineSlots<ChatMessageSlots & ChatMessageMarkdownSlots>()\n\nconst ns = useNamespace('chat-message')\nconst { t } = useLocale()\n\ntype AnyPart = ChatMessagePart | ChatCustomPart\n\nconst parts = computed<AnyPart[]>(\n () => props.parts ?? (props.content === undefined ? [] : [{ type: 'text', text: props.content }]),\n)\nconst markdownOptions = computed(() =>\n typeof props.markdown === 'object' ? props.markdown : undefined,\n)\n// `markdown-code` here is the Markdown component's `code`\nconst PREFIX = 'markdown-'\nconst markdownSlots = () =>\n Object.keys(slots)\n .filter((name) => name.startsWith(PREFIX))\n .map((name) => ({\n name: name as keyof ChatMessageMarkdownSlots,\n inner: name.slice(PREFIX.length),\n }))\nconst isUser = computed(() => props.sender === 'user')\nconst actionsShow = computed(() => props.actionsVisible ?? (isUser.value ? 'hover' : 'always'))\n// the person's attachments sit above their bubble, not inside it\nconst files = computed(() => (isUser.value ? parts.value.filter((p) => p.type === 'files') : []))\nconst body = computed(() =>\n isUser.value ? parts.value.filter((p) => p.type !== 'files') : parts.value,\n)\nconst text = computed(() => messageText(parts.value as { type: string; text?: unknown }[]))\n// Every source of the message, numbered in order, for `[1]` in Markdown\n// text. The same array while the sources stay the same, so text that\n// streams in does not redraw the whole reply.\nlet lastSources: ChatSource[] = []\nconst sources = computed(() => {\n const next = parts.value.flatMap((part) =>\n part.type === 'sources' ? ((part as { items?: ChatSource[] }).items ?? []) : [],\n )\n if (next.length === lastSources.length && next.every((s, i) => s === lastSources[i]))\n return lastSources\n return (lastSources = next)\n})\n\nconst busy = computed(\n () =>\n props.loading ||\n parts.value.some((part) => {\n const p = part as Record<string, unknown>\n // an artifact can keep being made after the reply ends, so it doesn't hold the actions back\n const working = p.status === 'running' && p.type !== 'artifact' && p.live !== false\n // busy exactly when steps or a plan show themselves running\n const live = p.live as boolean | undefined\n const steps = p.type === 'steps' && stepsLive(p.items as ChatStep[] | undefined, live)\n const plan = p.type === 'plan' && planRunning(p.items as ChatPlanItem[] | undefined, live)\n return !!p.streaming || !!p.thinking || steps || plan || working\n }),\n)\n\nconst innerVersion = ref(props.versions)\nconst version = computed(() => Math.min(props.version ?? innerVersion.value, props.versions))\nfunction step(delta: number) {\n const next = Math.min(Math.max(version.value + delta, 1), props.versions)\n innerVersion.value = next\n emit('update:version', next)\n}\n\nconst showActions = computed(\n () =>\n !busy.value &&\n (props.versions > 1 ||\n (props.copyable && !!text.value) ||\n (props.editable && isUser.value) ||\n !!slots.actions),\n)\n\n// editing in place\nconst editing = ref(false)\nconst draft = ref('')\nconst editorRef = useTemplateRef<HTMLTextAreaElement>('editor')\nfunction startEdit() {\n draft.value = text.value\n editing.value = true\n void nextTick(() => {\n const el = editorRef.value\n el?.focus()\n el?.setSelectionRange(el.value.length, el.value.length)\n fitEditor()\n })\n}\nfunction cancelEdit() {\n editing.value = false\n}\nfunction saveEdit() {\n if (!draft.value.trim()) return\n editing.value = false\n emit('edit', draft.value)\n}\nfunction fitEditor() {\n const el = editorRef.value\n if (!el) return\n el.style.height = 'auto'\n el.style.height = `${el.scrollHeight}px`\n}\nfunction onEditorKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEdit()\n } else if (event.key === 'Enter' && !event.shiftKey && !isComposing(event)) {\n event.preventDefault()\n saveEdit()\n }\n}\n\nconst asText = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'text' }>\nconst asReasoning = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'reasoning' }>\nconst asFiles = (part: AnyPart) => part as Extract<ChatMessagePart, { type: 'files' }>\nconst asConfirm = (part: AnyPart) => part as ChatConfirmPart\nconst asArtifact = (part: AnyPart) => part as ChatArtifactPart\nconst asQuestion = (part: AnyPart) => part as ChatQuestionPart\nconst known = new Set([\n 'text',\n 'reasoning',\n 'tool',\n 'steps',\n 'plan',\n 'sources',\n 'files',\n 'artifact',\n 'confirm',\n 'question',\n])\n// props for the part components, without the `type` discriminator\nconst rest = (part: AnyPart) => {\n const { type: _type, ...others } = part as Record<string, unknown>\n return others\n}\n</script>\n\n<template>\n <div\n :class=\"[\n ns.b(),\n ns.m(sender),\n ns.m(`actions-${actionsShow}`),\n ns.is('loading', loading),\n ns.is('editing', editing),\n ns.is('highlighted', highlighted),\n ]\"\n :aria-busy=\"busy || undefined\"\n :aria-current=\"highlighted || undefined\"\n >\n <div v-if=\"files.length && !editing\" :class=\"ns.e('files')\">\n <template v-for=\"(part, index) in files\" :key=\"`f${index}`\">\n <DouChatAttachments :model-value=\"asFiles(part).files\" :removable=\"false\" />\n </template>\n </div>\n\n <div v-if=\"editing\" :class=\"ns.e('editor')\">\n <textarea\n ref=\"editor\"\n v-model=\"draft\"\n :class=\"ns.e('editor-native')\"\n rows=\"1\"\n :aria-label=\"t('chat.edit')\"\n @input=\"fitEditor\"\n @keydown=\"onEditorKeydown\"\n />\n <div :class=\"ns.e('editor-actions')\">\n <DouButton size=\"sm\" variant=\"ghost\" @click=\"cancelEdit\">{{ t('chat.cancel') }}</DouButton>\n <DouButton size=\"sm\" :disabled=\"!draft.trim()\" @click=\"saveEdit\">\n {{ t('chat.send') }}\n </DouButton>\n </div>\n </div>\n\n <div v-else-if=\"body.length || loading || slots.default\" :class=\"ns.e('content')\">\n <slot>\n <template v-for=\"(part, index) in body\" :key=\"index\">\n <DouMarkdown\n v-if=\"part.type === 'text' && markdown && !slots.text\"\n v-bind=\"markdownOptions\"\n :class=\"ns.e('text')\"\n :content=\"asText(part).text\"\n :streaming=\"!!asText(part).streaming\"\n :sources=\"sources.length ? sources : undefined\"\n >\n <template v-for=\"slot in markdownSlots()\" :key=\"slot.name\" #[slot.inner]=\"scope\">\n <slot :name=\"slot.name\" v-bind=\"scope\" />\n </template>\n </DouMarkdown>\n <DouStreamText\n v-else-if=\"part.type === 'text'\"\n :class=\"ns.e('text')\"\n :content=\"asText(part).text\"\n :streaming=\"!!asText(part).streaming\"\n >\n <template v-if=\"slots.text\" #default=\"{ text: shown }\">\n <slot\n name=\"text\"\n :part=\"asText(part)\"\n :text=\"shown\"\n :index=\"index\"\n :sources=\"sources\"\n />\n </template>\n </DouStreamText>\n <DouChatReasoning\n v-else-if=\"part.type === 'reasoning'\"\n :thinking=\"!!asReasoning(part).thinking\"\n :duration=\"asReasoning(part).duration\"\n >\n <DouMarkdown\n v-if=\"markdown\"\n v-bind=\"markdownOptions\"\n :content=\"asReasoning(part).text\"\n :streaming=\"!!asReasoning(part).thinking\"\n :caret=\"false\"\n >\n <template v-for=\"slot in markdownSlots()\" :key=\"slot.name\" #[slot.inner]=\"scope\">\n <slot :name=\"slot.name\" v-bind=\"scope\" />\n </template>\n </DouMarkdown>\n <DouStreamText\n v-else\n :content=\"asReasoning(part).text\"\n :streaming=\"!!asReasoning(part).thinking\"\n :caret=\"false\"\n />\n </DouChatReasoning>\n <DouChatToolCall v-else-if=\"part.type === 'tool'\" v-bind=\"rest(part)\" />\n <DouChatSteps v-else-if=\"part.type === 'steps'\" v-bind=\"rest(part)\" />\n <DouChatPlan v-else-if=\"part.type === 'plan'\" v-bind=\"rest(part)\" />\n <DouChatSources v-else-if=\"part.type === 'sources'\" v-bind=\"rest(part)\" />\n <DouChatAttachments\n v-else-if=\"part.type === 'files'\"\n :model-value=\"asFiles(part).files\"\n :removable=\"false\"\n />\n <DouChatArtifact\n v-else-if=\"part.type === 'artifact'\"\n v-bind=\"rest(part)\"\n @open=\"emit('artifact', asArtifact(part))\"\n />\n <DouChatConfirm\n v-else-if=\"part.type === 'confirm'\"\n :title=\"asConfirm(part).title\"\n :description=\"asConfirm(part).description\"\n :status=\"asConfirm(part).status\"\n :allow-always=\"asConfirm(part).allowAlways\"\n :tone=\"asConfirm(part).tone\"\n @decide=\"emit('confirm', asConfirm(part), $event)\"\n >\n <pre v-if=\"asConfirm(part).detail\" :class=\"ns.e('code')\">{{\n asConfirm(part).detail\n }}</pre>\n </DouChatConfirm>\n <DouChatQuestion\n v-else-if=\"part.type === 'question'\"\n :title=\"asQuestion(part).title\"\n :description=\"asQuestion(part).description\"\n :options=\"asQuestion(part).options\"\n :status=\"asQuestion(part).status\"\n :answer=\"asQuestion(part).answer\"\n @select=\"(value, option) => emit('question', asQuestion(part), value, option)\"\n />\n <slot\n v-else-if=\"!known.has(part.type)\"\n name=\"part\"\n :part=\"part as ChatCustomPart\"\n :index=\"index\"\n />\n </template>\n </slot>\n <span v-if=\"loading\" :class=\"ns.e('loading')\" role=\"status\" :aria-label=\"t('chat.loading')\">\n <i /><i /><i />\n </span>\n </div>\n\n <div v-if=\"showActions && !editing\" :class=\"ns.e('actions')\">\n <div\n v-if=\"versions > 1\"\n :class=\"ns.e('versions')\"\n role=\"group\"\n :aria-label=\"t('chat.versions')\"\n >\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"version <= 1\"\n :aria-label=\"t('chat.previousVersion')\"\n @click=\"step(-1)\"\n >\n <ChevronLeftIcon />\n </DouButton>\n <span :class=\"ns.e('version')\">{{ version }} / {{ versions }}</span>\n <DouButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"version >= versions\"\n :aria-label=\"t('chat.nextVersion')\"\n @click=\"step(1)\"\n >\n <ChevronRightIcon />\n </DouButton>\n </div>\n <DouCopyButton v-if=\"copyable && text\" size=\"sm\" :text=\"text\" />\n <DouTooltip v-if=\"editable && isUser\" :content=\"t('chat.edit')\">\n <DouButton variant=\"ghost\" size=\"sm\" :aria-label=\"t('chat.edit')\" @click=\"startEdit\">\n <EditIcon />\n </DouButton>\n </DouTooltip>\n <slot name=\"actions\" />\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|