MessageActions.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <template>
  2. <!-- 消息操作栏:AI 与用户消息共用,按传入能力位决定显示哪些按钮 -->
  3. <div class="message-actions">
  4. <div class="action-btn" :title="copied ? '复制成功' : '复制'" @click="emit('copy')">
  5. <img :src="copied ? successIcon : copyIcon" width="14" height="14" />
  6. </div>
  7. <div v-if="canRegenerate" class="action-btn" @click="emit('regenerate')" title="重新生成">
  8. <img :src="getCssUrl('--img-chat-regenerate-icon')" width="14" height="14" />
  9. </div>
  10. <div v-if="canEdit" class="action-btn" @click="emit('edit')" title="编辑">
  11. <img :src="getCssUrl('--img-chat-edit-icon')" width="14" height="14" />
  12. </div>
  13. <div v-if="message.id" class="action-btn" @click="emit('delete')" title="删除消息">
  14. <img :src="getCssUrl('--img-chat-delete-icon')" width="14" height="14" />
  15. </div>
  16. <div v-if="showAnswerSwitcher" class="answer-switcher">
  17. <div class="action-btn answer-nav-btn" :class="{ 'is-disabled': !canPrev }" @click="emit('prevAnswer')" title="上一个回答">
  18. <img :src="getCssUrl('--img-chat-prev-icon')" width="14" height="14" />
  19. </div>
  20. <span class="answer-switcher-text">{{ answerPosition }}</span>
  21. <div class="action-btn answer-nav-btn" :class="{ 'is-disabled': !canNext }" @click="emit('nextAnswer')" title="下一个回答">
  22. <img :src="getCssUrl('--img-chat-next-icon')" width="14" height="14" />
  23. </div>
  24. </div>
  25. <span v-if="message.createdAt" class="action-time">{{ formatFullTime(message.createdAt) }}</span>
  26. </div>
  27. </template>
  28. <script setup lang="ts">
  29. import dayjs from 'dayjs';
  30. import type { Message } from '/@/views/ventAI/manageAssistent/types/index';
  31. import { getCssUrl } from '/@/views/ventAI/manageAssistent/lib/index';
  32. /**
  33. * 消息操作栏。显示与否(能否重新生成/编辑、多回答切换器、是否已复制)都是父组件的状态,
  34. * 这里只做呈现,所有点击以事件形式交回父组件。
  35. */
  36. defineProps<{
  37. message: Message;
  38. copied: boolean;
  39. canEdit?: boolean;
  40. canRegenerate?: boolean;
  41. showAnswerSwitcher?: boolean;
  42. canPrev?: boolean;
  43. canNext?: boolean;
  44. answerPosition?: string;
  45. }>();
  46. const emit = defineEmits<{
  47. (e: 'copy'): void;
  48. (e: 'delete'): void;
  49. (e: 'regenerate'): void;
  50. (e: 'edit'): void;
  51. (e: 'prevAnswer'): void;
  52. (e: 'nextAnswer'): void;
  53. }>();
  54. const copyIcon = getCssUrl('--img-chat-copy-icon');
  55. const successIcon = getCssUrl('--img-chat-copy-success-icon');
  56. const formatFullTime = (createdAt: string) => dayjs(createdAt).format('YYYY-MM-DD HH:mm:ss');
  57. </script>
  58. <style scoped lang="less">
  59. .message-actions {
  60. display: flex;
  61. align-items: center;
  62. gap: 8px;
  63. width: 100%;
  64. opacity: 0;
  65. transition: opacity 0.2s;
  66. .action-btn {
  67. width: 26px;
  68. height: 26px;
  69. display: flex;
  70. align-items: center;
  71. justify-content: center;
  72. border-radius: 4px;
  73. color: #8b949e;
  74. cursor: pointer;
  75. transition: all 0.2s;
  76. &:hover {
  77. color: #e0e6ed;
  78. background: rgba(10, 132, 255, 0.15);
  79. }
  80. }
  81. .action-time {
  82. font-size: 12px;
  83. color: #8b949e;
  84. }
  85. .answer-switcher {
  86. display: flex;
  87. align-items: center;
  88. gap: 2px;
  89. padding: 0 4px;
  90. border-radius: 4px;
  91. background: rgba(255, 255, 255, 0.04);
  92. .answer-nav-btn {
  93. width: 22px;
  94. height: 22px;
  95. &.is-disabled {
  96. opacity: 0.35;
  97. cursor: not-allowed;
  98. &:hover {
  99. background: transparent;
  100. color: #8b949e;
  101. }
  102. }
  103. }
  104. .answer-switcher-text {
  105. font-size: 12px;
  106. color: #8b949e;
  107. min-width: 32px;
  108. text-align: center;
  109. user-select: none;
  110. }
  111. }
  112. }
  113. </style>