ChatInputExtraContextItem.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { DocumentTextIcon, XMarkIcon } from '@heroicons/react/24/outline';
  2. import { MessageExtra } from '../utils/types';
  3. import { useState } from 'react';
  4. import { classNames } from '../utils/misc';
  5. export default function ChatInputExtraContextItem({
  6. items,
  7. removeItem,
  8. clickToShow,
  9. }: {
  10. items?: MessageExtra[];
  11. removeItem?: (index: number) => void;
  12. clickToShow?: boolean;
  13. }) {
  14. const [show, setShow] = useState(-1);
  15. const showingItem = show >= 0 ? items?.[show] : undefined;
  16. if (!items) return null;
  17. return (
  18. <div
  19. className="flex flex-row gap-4 overflow-x-auto py-2 px-1 mb-1"
  20. role="group"
  21. aria-description="Selected files"
  22. >
  23. {items.map((item, i) => (
  24. <div
  25. className="indicator"
  26. key={i}
  27. onClick={() => clickToShow && setShow(i)}
  28. tabIndex={0}
  29. aria-description={
  30. clickToShow ? `Click to show: ${item.name}` : undefined
  31. }
  32. role={clickToShow ? 'button' : 'menuitem'}
  33. >
  34. {removeItem && (
  35. <div className="indicator-item indicator-top">
  36. <button
  37. aria-label="Remove file"
  38. className="btn btn-neutral btn-sm w-4 h-4 p-0 rounded-full"
  39. onClick={() => removeItem(i)}
  40. >
  41. <XMarkIcon className="h-3 w-3" />
  42. </button>
  43. </div>
  44. )}
  45. <div
  46. className={classNames({
  47. 'flex flex-row rounded-md shadow-sm items-center m-0 p-0': true,
  48. 'cursor-pointer hover:shadow-md': !!clickToShow,
  49. })}
  50. >
  51. {item.type === 'imageFile' ? (
  52. <>
  53. <img
  54. src={item.base64Url}
  55. alt={`Preview image for ${item.name}`}
  56. className="w-14 h-14 object-cover rounded-md"
  57. />
  58. </>
  59. ) : (
  60. <>
  61. <div
  62. className="w-14 h-14 flex items-center justify-center"
  63. aria-description="Document icon"
  64. >
  65. <DocumentTextIcon className="h-8 w-14 text-base-content/50" />
  66. </div>
  67. <div className="text-xs pr-4">
  68. <b>{item.name ?? 'Extra content'}</b>
  69. </div>
  70. </>
  71. )}
  72. </div>
  73. </div>
  74. ))}
  75. {showingItem && (
  76. <dialog
  77. className="modal modal-open"
  78. aria-description={`Preview ${showingItem.name}`}
  79. >
  80. <div className="modal-box">
  81. <div className="flex justify-between items-center mb-4">
  82. <b>{showingItem.name ?? 'Extra content'}</b>
  83. <button
  84. className="btn btn-ghost btn-sm"
  85. aria-label="Close preview dialog"
  86. >
  87. <XMarkIcon className="h-5 w-5" onClick={() => setShow(-1)} />
  88. </button>
  89. </div>
  90. {showingItem.type === 'imageFile' ? (
  91. <img
  92. src={showingItem.base64Url}
  93. alt={`Preview image for ${showingItem.name}`}
  94. />
  95. ) : (
  96. <div className="overflow-x-auto">
  97. <pre className="whitespace-pre-wrap break-words text-sm">
  98. {showingItem.content}
  99. </pre>
  100. </div>
  101. )}
  102. </div>
  103. <div className="modal-backdrop" onClick={() => setShow(-1)}></div>
  104. </dialog>
  105. )}
  106. </div>
  107. );
  108. }