ChatHeader.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. "use client";
  2. import { formatConversationTime } from "@/utils/format";
  3. import { Button } from "@/components/ui/button";
  4. import { Separator } from "@/components/ui/separator";
  5. import { useI18n } from "@/lib/i18n/provider";
  6. import { cn } from "@/lib/utils";
  7. import { Bot } from "lucide-react";
  8. interface ChatHeaderProps {
  9. conversationId: number;
  10. lastSeenAt?: string | null;
  11. unreadCount: number;
  12. onMarkAllRead: () => void;
  13. onCloseConversation?: () => void;
  14. onRefresh: () => void;
  15. includeAIMessages?: boolean;
  16. onToggleAIMessages?: () => void;
  17. soundEnabled?: boolean;
  18. onToggleSound?: () => void;
  19. hideAIToggle?: boolean; // 内部对话时隐藏「显示 AI 消息」切换
  20. /** 为工作台移动端悬浮菜单(左/右圆钮)留出内边距,避免盖住标题与操作区 */
  21. mobileGutters?: { left?: boolean; right?: boolean };
  22. }
  23. export function ChatHeader({
  24. conversationId,
  25. lastSeenAt,
  26. unreadCount,
  27. onMarkAllRead,
  28. onCloseConversation,
  29. onRefresh,
  30. includeAIMessages = false,
  31. onToggleAIMessages,
  32. soundEnabled = false,
  33. onToggleSound,
  34. hideAIToggle = false,
  35. }: ChatHeaderProps) {
  36. const { t } = useI18n();
  37. return (
  38. <div className="h-16 flex items-center justify-between px-4 bg-background flex-shrink-0 relative">
  39. <div className="z-10">
  40. <div className="font-semibold text-foreground">
  41. {t("agent.chat.conversation")} #{conversationId}
  42. </div>
  43. <div className="text-xs text-muted-foreground mt-0.5">
  44. {lastSeenAt
  45. ? `${t("agent.chat.lastSeen")} ${formatConversationTime(lastSeenAt)}`
  46. : t("agent.chat.lastSeenUnknown")}
  47. </div>
  48. </div>
  49. <div className="flex items-center gap-1 sm:gap-2 flex-shrink-0">
  50. {/* 显示/隐藏 AI 消息切换按钮(内部对话不显示,默认始终包含 AI 消息) */}
  51. {onToggleAIMessages && !hideAIToggle && (
  52. <Button
  53. variant={includeAIMessages ? "default" : "outline"}
  54. size="sm"
  55. onClick={onToggleAIMessages}
  56. title={includeAIMessages ? t("agent.chat.hideAI") : t("agent.chat.showAI")}
  57. aria-label={includeAIMessages ? t("agent.chat.hideAI") : t("agent.chat.showAI")}
  58. className="text-xs gap-1 px-2 sm:px-3"
  59. >
  60. <Bot className="h-4 w-4 sm:hidden shrink-0" aria-hidden />
  61. <span className="hidden sm:inline">
  62. {includeAIMessages ? t("agent.chat.hideAI") : t("agent.chat.showAI")}
  63. </span>
  64. </Button>
  65. )}
  66. {/* 声音开关按钮 */}
  67. {onToggleSound && (
  68. <Button
  69. variant="ghost"
  70. size="icon"
  71. title={soundEnabled ? t("agent.chat.soundOn") : t("agent.chat.soundOff")}
  72. onClick={onToggleSound}
  73. >
  74. {soundEnabled ? (
  75. <svg
  76. className="w-5 h-5"
  77. fill="none"
  78. stroke="currentColor"
  79. viewBox="0 0 24 24"
  80. >
  81. <path
  82. strokeLinecap="round"
  83. strokeLinejoin="round"
  84. strokeWidth={2}
  85. d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
  86. />
  87. </svg>
  88. ) : (
  89. <svg
  90. className="w-5 h-5"
  91. fill="none"
  92. stroke="currentColor"
  93. viewBox="0 0 24 24"
  94. >
  95. <path
  96. strokeLinecap="round"
  97. strokeLinejoin="round"
  98. strokeWidth={2}
  99. d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
  100. />
  101. <path
  102. strokeLinecap="round"
  103. strokeLinejoin="round"
  104. strokeWidth={2}
  105. d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"
  106. />
  107. </svg>
  108. )}
  109. </Button>
  110. )}
  111. <Button
  112. variant="ghost"
  113. size="icon"
  114. title={t("agent.chat.closeConversation")}
  115. onClick={onCloseConversation}
  116. disabled={!onCloseConversation}
  117. >
  118. <svg
  119. className="w-5 h-5"
  120. fill="none"
  121. stroke="currentColor"
  122. viewBox="0 0 24 24"
  123. >
  124. <path
  125. strokeLinecap="round"
  126. strokeLinejoin="round"
  127. strokeWidth={2}
  128. d="M6 6l12 12M18 6L6 18"
  129. />
  130. </svg>
  131. </Button>
  132. <Button
  133. variant="ghost"
  134. size="icon"
  135. title={t("agent.chat.refresh")}
  136. onClick={onRefresh}
  137. >
  138. <svg
  139. className="w-5 h-5 text-muted-foreground"
  140. fill="none"
  141. stroke="currentColor"
  142. viewBox="0 0 24 24"
  143. >
  144. <path
  145. strokeLinecap="round"
  146. strokeLinejoin="round"
  147. strokeWidth={2}
  148. d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
  149. />
  150. </svg>
  151. </Button>
  152. </div>
  153. <Separator className="absolute bottom-0 left-0 right-0" />
  154. </div>
  155. );
  156. }