ChatCard.vue
1 <script setup lang="tsx"> 2 import { ref } from 'vue' 3 import { useSnackbarStore } from '@/renderer/store/snackbar' 4 5 interface MessageContentItem { 6 type: string 7 text: string 8 } 9 10 interface Message { 11 content: string | MessageContentItem[] 12 } 13 14 const props = defineProps({ 15 index: { type: Number, required: true }, 16 range: { type: Number, default: 1 }, 17 messages: { type: Object, required: true }, 18 showContent: { type: Boolean, default: false }, 19 showDelete: { type: Boolean, default: true }, 20 showReduce: { type: Boolean, default: true }, 21 showModify: { type: Boolean, default: false }, 22 showCopy: { type: Boolean, default: true } 23 }) 24 25 const emit = defineEmits<{ 26 (_e: 'delete-messages', _payload: { index: number; range: number }): void 27 }>() 28 29 const emitDeleteMessage = () => { 30 emit('delete-messages', { 31 index: props.index, 32 range: props.range 33 }) 34 } 35 36 const emitDeleteMessageRange = () => { 37 emit('delete-messages', { 38 index: 0, 39 range: props.index 40 }) 41 } 42 43 const showcontent = ref(false) 44 const showmodify = ref(false) 45 const snackbarStore = useSnackbarStore() 46 47 const copyToClipboard = async (msg: Message) => { 48 let textToCopy = '' 49 50 try { 51 if (typeof msg.content === 'string') { 52 textToCopy = msg.content 53 } else if (Array.isArray(msg.content)) { 54 for (const item of msg.content) { 55 if (item.type === 'text' && typeof item.text === 'string') { 56 textToCopy = item.text 57 break // 只取第一个文本内容 58 } 59 } 60 } 61 62 await navigator.clipboard.writeText(textToCopy) 63 snackbarStore.showSuccessMessage('snackbar.copied') 64 } catch (err) { 65 snackbarStore.showErrorMessage(err instanceof Error ? err.message : String(err)) 66 } 67 } 68 </script> 69 <template> 70 <v-hover open-delay="100"> 71 <template #default="{ isHovering, props: hoverProps }"> 72 <v-card v-bind="hoverProps" :elevation="isHovering ? 4 : 2" width="100vw" max-width="100%"> 73 <slot :showcontent="showcontent" :showmodify="showmodify" /> 74 <v-expand-transition> 75 <div v-if="isHovering"> 76 <v-divider /> 77 <v-card-actions> 78 <!-- Copy Button --> 79 <v-btn 80 v-if="showCopy" 81 color="primary" 82 icon="mdi-content-copy" 83 size="x-small" 84 variant="plain" 85 @click="copyToClipboard(messages[index])" 86 /> 87 88 <!-- Modify Button --> 89 <v-btn 90 v-if="showModify" 91 color="primary" 92 :icon="showmodify ? 'mdi-check-bold' : 'mdi-lead-pencil'" 93 size="x-small" 94 variant="plain" 95 @click="showmodify = !showmodify" 96 /> 97 98 <!-- Content Toggle Button --> 99 <v-btn 100 v-if="showContent" 101 color="primary" 102 :icon="showcontent ? 'mdi-eye-remove' : 'mdi-eye'" 103 size="x-small" 104 variant="plain" 105 @click="showcontent = !showcontent" 106 /> 107 108 <v-spacer /> 109 110 <!-- Reduce Button --> 111 <v-btn 112 v-if="showReduce && index > 0" 113 v-tooltip:top="$t('chat.reduce')" 114 color="error" 115 icon="mdi-format-align-top" 116 size="x-small" 117 variant="plain" 118 @click="emitDeleteMessageRange" 119 /> 120 121 <!-- Delete Button --> 122 <v-btn 123 v-if="showDelete" 124 color="error" 125 icon="mdi-delete-outline" 126 size="x-small" 127 variant="plain" 128 @click="emitDeleteMessage" 129 /> 130 </v-card-actions> 131 </div> 132 </v-expand-transition> 133 </v-card> 134 </template> 135 </v-hover> 136 </template>