206 lines
4.6 KiB
Vue
206 lines
4.6 KiB
Vue
<script setup lang="ts">
|
|
import { onBeforeUnmount, ref } from 'vue';
|
|
|
|
import { Check, Copy, RotateCw } from '@easyflow/icons';
|
|
|
|
import ChatVariantNavigator from './ChatVariantNavigator.vue';
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
align?: 'end' | 'start';
|
|
allowCopy?: boolean;
|
|
allowRegenerate?: boolean;
|
|
copyAction?: () => boolean | Promise<boolean>;
|
|
disabledVariantNext?: boolean;
|
|
disabledVariantPrevious?: boolean;
|
|
regenerateDisabled?: boolean;
|
|
showVariantNavigator?: boolean;
|
|
variantCurrent?: number;
|
|
variantLoading?: boolean;
|
|
variantTotal?: number;
|
|
}>(),
|
|
{
|
|
align: 'start',
|
|
allowCopy: false,
|
|
allowRegenerate: false,
|
|
copyAction: undefined,
|
|
disabledVariantNext: false,
|
|
disabledVariantPrevious: false,
|
|
regenerateDisabled: false,
|
|
showVariantNavigator: false,
|
|
variantCurrent: 1,
|
|
variantLoading: false,
|
|
variantTotal: 1,
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
copy: [];
|
|
regenerate: [];
|
|
selectNextVariant: [];
|
|
selectPreviousVariant: [];
|
|
}>();
|
|
|
|
const copied = ref(false);
|
|
const copying = ref(false);
|
|
const COPIED_FEEDBACK_DURATION_MS = 3000;
|
|
let copiedResetTimer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
async function handleCopy() {
|
|
if (copying.value) {
|
|
return;
|
|
}
|
|
if (!props.copyAction) {
|
|
emit('copy');
|
|
return;
|
|
}
|
|
|
|
copying.value = true;
|
|
try {
|
|
const success = await props.copyAction();
|
|
if (!success) {
|
|
return;
|
|
}
|
|
copied.value = true;
|
|
if (copiedResetTimer) {
|
|
clearTimeout(copiedResetTimer);
|
|
}
|
|
copiedResetTimer = setTimeout(() => {
|
|
copied.value = false;
|
|
copiedResetTimer = undefined;
|
|
}, COPIED_FEEDBACK_DURATION_MS);
|
|
} catch (error) {
|
|
console.error('复制消息失败:', error);
|
|
} finally {
|
|
copying.value = false;
|
|
}
|
|
}
|
|
|
|
function handleRegenerate() {
|
|
if (props.regenerateDisabled) {
|
|
return;
|
|
}
|
|
emit('regenerate');
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
if (copiedResetTimer) {
|
|
clearTimeout(copiedResetTimer);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
v-if="allowCopy || allowRegenerate || showVariantNavigator"
|
|
class="chat-message-toolbar"
|
|
:class="`is-${align}`"
|
|
>
|
|
<button
|
|
v-if="allowCopy"
|
|
type="button"
|
|
class="chat-message-toolbar__button"
|
|
:aria-busy="copying"
|
|
:aria-label="copied ? '复制成功' : '复制消息'"
|
|
:title="copied ? '复制成功' : '复制'"
|
|
@click="handleCopy"
|
|
>
|
|
<Transition name="chat-message-toolbar__icon" mode="out-in">
|
|
<Check v-if="copied" key="check" />
|
|
<Copy v-else key="copy" />
|
|
</Transition>
|
|
</button>
|
|
<button
|
|
v-if="allowRegenerate"
|
|
type="button"
|
|
class="chat-message-toolbar__button"
|
|
:disabled="regenerateDisabled"
|
|
aria-label="重新生成"
|
|
title="重新生成"
|
|
@click="handleRegenerate"
|
|
>
|
|
<RotateCw />
|
|
</button>
|
|
<ChatVariantNavigator
|
|
v-if="showVariantNavigator"
|
|
class="chat-message-toolbar__navigator"
|
|
:current="variantCurrent"
|
|
:total="variantTotal"
|
|
:disabled-next="disabledVariantNext"
|
|
:disabled-previous="disabledVariantPrevious"
|
|
:loading="variantLoading"
|
|
@next="emit('selectNextVariant')"
|
|
@previous="emit('selectPreviousVariant')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chat-message-toolbar {
|
|
display: flex;
|
|
width: 100%;
|
|
gap: 4px;
|
|
align-items: center;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.chat-message-toolbar.is-end {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.chat-message-toolbar__navigator {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.chat-message-toolbar__button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
padding: 0;
|
|
color: var(--el-text-color-secondary);
|
|
cursor: pointer;
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: 6px;
|
|
transition:
|
|
color 0.18s ease,
|
|
background-color 0.18s ease,
|
|
opacity 0.18s ease;
|
|
}
|
|
|
|
.chat-message-toolbar__button:hover:not(:disabled) {
|
|
color: var(--el-text-color-primary);
|
|
background: var(--el-fill-color-light);
|
|
}
|
|
|
|
.chat-message-toolbar__button:focus-visible {
|
|
outline: 2px solid var(--el-color-primary-light-5);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.chat-message-toolbar__button:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.42;
|
|
}
|
|
|
|
.chat-message-toolbar__button :deep(svg) {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.chat-message-toolbar__icon-enter-active,
|
|
.chat-message-toolbar__icon-leave-active {
|
|
transition:
|
|
opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
transform var(--motion-duration-fast) var(--motion-ease-standard);
|
|
}
|
|
|
|
.chat-message-toolbar__icon-enter-from,
|
|
.chat-message-toolbar__icon-leave-to {
|
|
opacity: 0;
|
|
transform: scale(0.88);
|
|
}
|
|
</style>
|