✨(front) add drag'n drop file (#110)
add drag n drop in input chat on the entire screen
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(front) add drag'n drop file
|
||||
- ✨(activation-codes) register users also on Brevo #98
|
||||
- 📈(posthog) add `sub` field to tracking #95
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
<svg width="118" height="86" viewBox="0 0 118 86" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_13649_715)">
|
||||
<g filter="url(#filter1_d_13649_715)">
|
||||
<g clip-path="url(#clip0_13649_715)">
|
||||
<rect x="67.1685" y="17.71" width="37.9734" height="30.9413" rx="4.96294" transform="rotate(15.4037 67.1685 17.71)" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.7995 26.0047C70.9944 25.2975 71.7257 24.8821 72.4329 25.0769L83.2801 28.0655C83.9874 28.2604 84.4027 28.9917 84.2079 29.6989C84.013 30.4062 83.2817 30.8215 82.5745 30.6267L71.7273 27.6381C71.02 27.4432 70.6047 26.7119 70.7995 26.0047Z" fill="#AE6257"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.7995 26.0047C70.9944 25.2975 71.7257 24.8821 72.4329 25.0769L83.2801 28.0655C83.9874 28.2604 84.4027 28.9917 84.2079 29.6989C84.013 30.4062 83.2817 30.8215 82.5745 30.6267L71.7273 27.6381C71.02 27.4432 70.6047 26.7119 70.7995 26.0047Z" fill="#F6F8F9" fill-opacity="0.15"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M69.3049 31.428C69.4998 30.7208 70.231 30.3054 70.9383 30.5003L93.9886 36.851C94.6958 37.0459 95.1112 37.7772 94.9164 38.4844C94.7215 39.1916 93.9902 39.607 93.283 39.4122L70.2327 33.0614C69.5254 32.8666 69.11 32.1353 69.3049 31.428Z" fill="#AE6257"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M69.3049 31.428C69.4998 30.7208 70.231 30.3054 70.9383 30.5003L93.9886 36.851C94.6958 37.0459 95.1112 37.7772 94.9164 38.4844C94.7215 39.1916 93.9902 39.607 93.283 39.4122L70.2327 33.0614C69.5254 32.8666 69.11 32.1353 69.3049 31.428Z" fill="#F6F8F9" fill-opacity="0.8"/>
|
||||
</g>
|
||||
<rect x="67.4896" y="18.2754" width="37.0538" height="30.0217" rx="4.50314" transform="rotate(15.4037 67.4896 18.2754)" stroke="#AE6257" stroke-width="0.919604"/>
|
||||
<rect x="67.4896" y="18.2754" width="37.0538" height="30.0217" rx="4.50314" transform="rotate(15.4037 67.4896 18.2754)" stroke="#F6F8F9" stroke-opacity="0.8" stroke-width="0.919604"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_13649_715)">
|
||||
<g clip-path="url(#clip1_13649_715)">
|
||||
<rect x="13.6865" y="22.7539" width="30.5799" height="40.7732" rx="4.44697" transform="rotate(-15.6072 13.6865 22.7539)" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5742 30.4375C20.406 29.8351 20.7579 29.2102 21.3604 29.042L31.1778 26.2995C31.7803 26.1312 32.4051 26.4832 32.5734 27.0857C32.7417 27.6881 32.3897 28.3129 31.7873 28.4812L21.9698 31.2236C21.3673 31.3919 20.7425 31.04 20.5742 30.4375Z" fill="#3677CC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5742 30.4375C20.406 29.8351 20.7579 29.2102 21.3604 29.042L31.1778 26.2995C31.7803 26.1312 32.4051 26.4832 32.5734 27.0857C32.7417 27.6881 32.3897 28.3129 31.7873 28.4812L21.9698 31.2236C21.3673 31.3919 20.7425 31.04 20.5742 30.4375Z" fill="#F6F8F9" fill-opacity="0.15"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.6045 34.127C21.4362 33.5245 21.7882 32.8997 22.3906 32.7314L40.8655 27.5706C41.468 27.4023 42.0928 27.7543 42.261 28.3567C42.4293 28.9592 42.0774 29.584 41.4749 29.7523L23.0001 34.9131C22.3976 35.0814 21.7728 34.7294 21.6045 34.127ZM22.5187 37.3995C22.3504 36.797 22.7023 36.1722 23.3048 36.0039L41.7796 30.8431C42.3821 30.6748 43.0069 31.0268 43.1752 31.6292C43.3435 32.2317 42.9915 32.8565 42.3891 33.0248L23.9142 38.1856C23.3118 38.3539 22.6869 38.0019 22.5187 37.3995ZM23.4328 40.6719C23.2645 40.0695 23.6165 39.4447 24.2189 39.2764L42.6938 34.1156C43.2962 33.9473 43.921 34.2993 44.0893 34.9017C44.2576 35.5042 43.9057 36.129 43.3032 36.2973L24.8283 41.4581C24.2259 41.6263 23.6011 41.2744 23.4328 40.6719ZM24.3469 43.9444C24.1787 43.342 24.5306 42.7172 25.1331 42.5489L43.6079 37.3881C44.2104 37.2198 44.8352 37.5718 45.0035 38.1742C45.1718 38.7767 44.8198 39.4015 44.2174 39.5698L25.7425 44.7305C25.14 44.8988 24.5152 44.5469 24.3469 43.9444Z" fill="#3677CC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.6045 34.127C21.4362 33.5245 21.7882 32.8997 22.3906 32.7314L40.8655 27.5706C41.468 27.4023 42.0928 27.7543 42.261 28.3567C42.4293 28.9592 42.0774 29.584 41.4749 29.7523L23.0001 34.9131C22.3976 35.0814 21.7728 34.7294 21.6045 34.127ZM22.5187 37.3995C22.3504 36.797 22.7023 36.1722 23.3048 36.0039L41.7796 30.8431C42.3821 30.6748 43.0069 31.0268 43.1752 31.6292C43.3435 32.2317 42.9915 32.8565 42.3891 33.0248L23.9142 38.1856C23.3118 38.3539 22.6869 38.0019 22.5187 37.3995ZM23.4328 40.6719C23.2645 40.0695 23.6165 39.4447 24.2189 39.2764L42.6938 34.1156C43.2962 33.9473 43.921 34.2993 44.0893 34.9017C44.2576 35.5042 43.9057 36.129 43.3032 36.2973L24.8283 41.4581C24.2259 41.6263 23.6011 41.2744 23.4328 40.6719ZM24.3469 43.9444C24.1787 43.342 24.5306 42.7172 25.1331 42.5489L43.6079 37.3881C44.2104 37.2198 44.8352 37.5718 45.0035 38.1742C45.1718 38.7767 44.8198 39.4015 44.2174 39.5698L25.7425 44.7305C25.14 44.8988 24.5152 44.5469 24.3469 43.9444Z" fill="#F6F8F9" fill-opacity="0.8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.2719 47.2555C25.1036 46.653 25.4555 46.0282 26.058 45.86L44.5328 40.6992C45.1353 40.5309 45.7601 40.8828 45.9284 41.4853C46.0967 42.0877 45.7447 42.7125 45.1423 42.8808L26.6674 48.0416C26.065 48.2099 25.4401 47.8579 25.2719 47.2555ZM26.186 50.528C26.0177 49.9255 26.3697 49.3007 26.9721 49.1324L45.447 43.9716C46.0494 43.8034 46.6742 44.1553 46.8425 44.7578C47.0108 45.3602 46.6589 45.985 46.0564 46.1533L27.5815 51.3141C26.9791 51.4824 26.3543 51.1304 26.186 50.528ZM27.1001 53.8005C26.9319 53.198 27.2838 52.5732 27.8863 52.4049L46.3611 47.2441C46.9636 47.0759 47.5884 47.4278 47.7567 48.0303C47.925 48.6327 47.573 49.2575 46.9706 49.4258L28.4957 54.5866C27.8932 54.7549 27.2684 54.4029 27.1001 53.8005ZM28.0143 57.073C27.846 56.4705 28.1979 55.8457 28.8004 55.6774L47.2753 50.5166C47.8777 50.3483 48.5025 50.7003 48.6708 51.3027C48.8391 51.9052 48.4871 52.53 47.8847 52.6983L29.4098 57.8591C28.8074 58.0274 28.1826 57.6754 28.0143 57.073Z" fill="#3677CC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.2719 47.2555C25.1036 46.653 25.4555 46.0282 26.058 45.86L44.5328 40.6992C45.1353 40.5309 45.7601 40.8828 45.9284 41.4853C46.0967 42.0877 45.7447 42.7125 45.1423 42.8808L26.6674 48.0416C26.065 48.2099 25.4401 47.8579 25.2719 47.2555ZM26.186 50.528C26.0177 49.9255 26.3697 49.3007 26.9721 49.1324L45.447 43.9716C46.0494 43.8034 46.6742 44.1553 46.8425 44.7578C47.0108 45.3602 46.6589 45.985 46.0564 46.1533L27.5815 51.3141C26.9791 51.4824 26.3543 51.1304 26.186 50.528ZM27.1001 53.8005C26.9319 53.198 27.2838 52.5732 27.8863 52.4049L46.3611 47.2441C46.9636 47.0759 47.5884 47.4278 47.7567 48.0303C47.925 48.6327 47.573 49.2575 46.9706 49.4258L28.4957 54.5866C27.8932 54.7549 27.2684 54.4029 27.1001 53.8005ZM28.0143 57.073C27.846 56.4705 28.1979 55.8457 28.8004 55.6774L47.2753 50.5166C47.8777 50.3483 48.5025 50.7003 48.6708 51.3027C48.8391 51.9052 48.4871 52.53 47.8847 52.6983L29.4098 57.8591C28.8074 58.0274 28.1826 57.6754 28.0143 57.073Z" fill="#F6F8F9" fill-opacity="0.8"/>
|
||||
</g>
|
||||
<rect x="14.2259" y="23.0577" width="29.7044" height="39.8977" rx="4.00923" transform="rotate(-15.6072 14.2259 23.0577)" stroke="#3677CC" stroke-width="0.875498"/>
|
||||
<rect x="14.2259" y="23.0577" width="29.7044" height="39.8977" rx="4.00923" transform="rotate(-15.6072 14.2259 23.0577)" stroke="#F6F8F9" stroke-opacity="0.8" stroke-width="0.875498"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_13649_715)">
|
||||
<rect x="38.4051" y="37.3387" width="39.5447" height="32.0173" rx="5.40236" fill="#6D778C"/>
|
||||
<rect x="38.4051" y="37.3387" width="39.5447" height="32.0173" rx="5.40236" fill="#F6F8F9" fill-opacity="0.975"/>
|
||||
<rect x="38.4051" y="37.3387" width="39.5447" height="32.0173" rx="5.40236" stroke="#6D778C" stroke-width="1.10324"/>
|
||||
<rect x="38.4051" y="37.3387" width="39.5447" height="32.0173" rx="5.40236" stroke="#F6F8F9" stroke-opacity="0.8" stroke-width="1.10324"/>
|
||||
<path d="M50.8763 49.2972C51.4755 49.6412 52.1356 49.8131 52.8568 49.8131C53.5779 49.8131 54.2325 49.6412 54.8206 49.2972C55.4086 48.9422 55.8801 48.4651 56.2351 47.866C56.5902 47.2669 56.7677 46.6067 56.7677 45.8856C56.7677 45.1755 56.5902 44.5209 56.2351 43.9218C55.8801 43.3227 55.4086 42.8456 54.8206 42.4906C54.2325 42.1355 53.5779 41.958 52.8568 41.958C52.1356 41.958 51.4755 42.1355 50.8763 42.4906C50.2883 42.8456 49.8168 43.3227 49.4618 43.9218C49.1067 44.5209 48.9292 45.1755 48.9292 45.8856C48.9292 46.6067 49.1067 47.2669 49.4618 47.866C49.8168 48.4651 50.2883 48.9422 50.8763 49.2972Z" fill="#9B6F02"/>
|
||||
<path d="M50.8763 49.2972C51.4755 49.6412 52.1356 49.8131 52.8568 49.8131C53.5779 49.8131 54.2325 49.6412 54.8206 49.2972C55.4086 48.9422 55.8801 48.4651 56.2351 47.866C56.5902 47.2669 56.7677 46.6067 56.7677 45.8856C56.7677 45.1755 56.5902 44.5209 56.2351 43.9218C55.8801 43.3227 55.4086 42.8456 54.8206 42.4906C54.2325 42.1355 53.5779 41.958 52.8568 41.958C52.1356 41.958 51.4755 42.1355 50.8763 42.4906C50.2883 42.8456 49.8168 43.3227 49.4618 43.9218C49.1067 44.5209 48.9292 45.1755 48.9292 45.8856C48.9292 46.6067 49.1067 47.2669 49.4618 47.866C49.8168 48.4651 50.2883 48.9422 50.8763 49.2972Z" fill="#F6F8F9" fill-opacity="0.15"/>
|
||||
<path d="M47.5929 55.7846C48.3745 54.6792 48.7652 54.1265 49.2447 53.929C49.6647 53.7561 50.1308 53.7561 50.5507 53.929C51.0302 54.1265 51.4209 54.6792 52.2025 55.7846L55.0263 59.7786C56.1326 61.3434 56.6858 62.1258 56.6721 62.7805C56.6602 63.3502 56.407 63.8842 55.9822 64.2353C55.4941 64.6388 54.5699 64.6388 52.7215 64.6388H47.0739C45.2255 64.6388 44.3013 64.6388 43.8132 64.2353C43.3884 63.8842 43.1352 63.3502 43.1233 62.7805C43.1096 62.1258 43.6628 61.3434 44.7691 59.7786L47.5929 55.7846Z" fill="#5A8228"/>
|
||||
<path d="M47.5929 55.7846C48.3745 54.6792 48.7652 54.1265 49.2447 53.929C49.6647 53.7561 50.1308 53.7561 50.5507 53.929C51.0302 54.1265 51.4209 54.6792 52.2025 55.7846L55.0263 59.7786C56.1326 61.3434 56.6858 62.1258 56.6721 62.7805C56.6602 63.3502 56.407 63.8842 55.9822 64.2353C55.4941 64.6388 54.5699 64.6388 52.7215 64.6388H47.0739C45.2255 64.6388 44.3013 64.6388 43.8132 64.2353C43.3884 63.8842 43.1352 63.3502 43.1233 62.7805C43.1096 62.1258 43.6628 61.3434 44.7691 59.7786L47.5929 55.7846Z" fill="#F6F8F9" fill-opacity="0.55"/>
|
||||
<path d="M60.0205 49.0171C60.8418 47.6462 61.2525 46.9607 61.7796 46.7263C62.2398 46.5216 62.7614 46.5216 63.2216 46.7263C63.7487 46.9607 64.1594 47.6462 64.9807 49.0171L71.586 60.0431C72.4852 61.5441 72.9348 62.2945 72.8846 62.9134C72.8409 63.4528 72.5744 63.9468 72.153 64.2689C71.6697 64.6385 70.8151 64.6385 69.106 64.6385H55.8952C54.1861 64.6385 53.3315 64.6385 52.8482 64.2689C52.4268 63.9468 52.1603 63.4528 52.1166 62.9134C52.0664 62.2945 52.516 61.5441 53.4152 60.0431L60.0205 49.0171Z" fill="#5A8228"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_13649_715" x="0.0778322" y="0.618848" width="117.844" height="84.7633" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="4.35"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.243137 0 0 0 0 0.364706 0 0 0 0 0.905882 0 0 0 0.1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_13649_715"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_13649_715" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d_13649_715" x="48.6245" y="6.69141" width="65.8525" height="65.853" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.62744"/>
|
||||
<feGaussianBlur stdDeviation="2.62744"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0941176 0 0 0 0 0.105882 0 0 0 0 0.141176 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_13649_715"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_13649_715" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter2_d_13649_715" x="3.77499" y="10.6539" width="60.2454" height="60.2454" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2.50142"/>
|
||||
<feGaussianBlur stdDeviation="2.50142"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0941176 0 0 0 0 0.105882 0 0 0 0 0.141176 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_13649_715"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_13649_715" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter3_d_13649_715" x="27.7856" y="25.3547" width="60.7837" height="60.7837" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="3.15211"/>
|
||||
<feGaussianBlur stdDeviation="3.15211"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0941176 0 0 0 0 0.105882 0 0 0 0 0.141176 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_13649_715"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_13649_715" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_13649_715">
|
||||
<rect x="67.1685" y="17.71" width="37.9734" height="30.9413" rx="4.96294" transform="rotate(15.4037 67.1685 17.71)" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_13649_715">
|
||||
<rect x="13.6865" y="22.7539" width="30.5799" height="40.7732" rx="4.44697" transform="rotate(-15.6072 13.6865 22.7539)" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -8,6 +8,8 @@ import { LLMModel } from '@/features/chat/api/useLLMConfiguration';
|
||||
import { useAnalytics } from '@/libs';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import FilesIcon from '../assets/files.svg';
|
||||
|
||||
import { AttachmentList } from './AttachmentList';
|
||||
import { ModelSelector } from './ModelSelector';
|
||||
import { ScrollDown } from './ScrollDown';
|
||||
@@ -128,157 +130,243 @@ export const InputChat = ({
|
||||
}
|
||||
}, [status, input]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fileUploadEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleDragEnter = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer?.types.includes('Files')) {
|
||||
setIsDragActive(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
// Only hide when leaving the window completely
|
||||
if (!e.relatedTarget) {
|
||||
setIsDragActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleDrop = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(false);
|
||||
|
||||
if (!fileUploadEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const droppedFiles = e.dataTransfer?.files;
|
||||
if (droppedFiles && droppedFiles.length > 0) {
|
||||
setFiles((prev) => {
|
||||
const dt = new DataTransfer();
|
||||
if (prev) {
|
||||
Array.from(prev).forEach((f) => dt.items.add(f));
|
||||
}
|
||||
Array.from(droppedFiles).forEach((f) => {
|
||||
if (
|
||||
!Array.from(prev || []).some(
|
||||
(pf) =>
|
||||
pf.name === f.name &&
|
||||
pf.size === f.size &&
|
||||
pf.lastModified === f.lastModified,
|
||||
)
|
||||
) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
return dt.files;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('dragenter', handleDragEnter);
|
||||
window.addEventListener('dragleave', handleDragLeave);
|
||||
window.addEventListener('dragover', handleDragOver);
|
||||
window.addEventListener('drop', handleDrop);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('dragenter', handleDragEnter);
|
||||
window.removeEventListener('dragleave', handleDragLeave);
|
||||
window.removeEventListener('dragover', handleDragOver);
|
||||
window.removeEventListener('drop', handleDrop);
|
||||
};
|
||||
}, [fileUploadEnabled, setFiles]);
|
||||
|
||||
const isInputDisabled = status !== 'ready' || isUploadingFiles;
|
||||
|
||||
return (
|
||||
<Box
|
||||
$css={`
|
||||
display: block;
|
||||
position: relative;
|
||||
opacity: ${status === 'error' ? '0.5' : '1'};
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
padding: ${isDesktop ? '0' : '0 10px'};
|
||||
max-width: 750px;
|
||||
`}
|
||||
>
|
||||
{/* Bouton de scroll vers le bas */}
|
||||
{messagesLength > 1 &&
|
||||
status !== 'streaming' &&
|
||||
status !== 'submitted' &&
|
||||
containerRef &&
|
||||
onScrollToBottom && (
|
||||
<Box
|
||||
$css={`
|
||||
<>
|
||||
{isDragActive && (
|
||||
<Box
|
||||
$position="fixed"
|
||||
$css={`
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
animation: fadeIn 0.3s;
|
||||
z-index: 999;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
pointer-events: all;
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
$css={`
|
||||
display: block;
|
||||
position: relative;
|
||||
opacity: ${status === 'error' ? '0.5' : '1'};
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
padding: ${isDesktop ? '0' : '0 10px'};
|
||||
max-width: 750px;
|
||||
`}
|
||||
>
|
||||
{/* Bouton de scroll vers le bas */}
|
||||
{messagesLength > 1 &&
|
||||
status !== 'streaming' &&
|
||||
status !== 'submitted' &&
|
||||
containerRef &&
|
||||
onScrollToBottom && (
|
||||
<Box
|
||||
$css={`
|
||||
position: relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
max-width: 750px;
|
||||
`}
|
||||
>
|
||||
<ScrollDown
|
||||
onClick={onScrollToBottom}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{/* Message de bienvenue */}
|
||||
{messagesLength === 0 && (
|
||||
<Box
|
||||
$padding={{ all: 'base', bottom: 'md' }}
|
||||
$align="center"
|
||||
$margin={{ horizontal: 'base', bottom: 'md', top: '-105px' }}
|
||||
>
|
||||
<ScrollDown
|
||||
onClick={onScrollToBottom}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
<Text as="h2" $size="xl" $weight="600" $margin={{ all: '0' }}>
|
||||
{t('What is on your mind?')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{/* Message de bienvenue */}
|
||||
{messagesLength === 0 && (
|
||||
<Box
|
||||
$padding={{ all: 'base', bottom: 'md' }}
|
||||
$align="center"
|
||||
$margin={{ horizontal: 'base', bottom: 'md', top: '-105px' }}
|
||||
>
|
||||
<Text as="h2" $size="xl" $weight="600" $margin={{ all: '0' }}>
|
||||
{t('What is on your mind?')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(fileUploadEnabled);
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(false);
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(false);
|
||||
if (!fileUploadEnabled) {
|
||||
return;
|
||||
}
|
||||
if (e.dataTransfer.files?.length) {
|
||||
setFiles((prev) => {
|
||||
const dt = new DataTransfer();
|
||||
if (prev) {
|
||||
Array.from(prev).forEach((f) => dt.items.add(f));
|
||||
}
|
||||
Array.from(e.dataTransfer.files).forEach((f) => {
|
||||
if (
|
||||
!Array.from(prev || []).some(
|
||||
(pf) =>
|
||||
pf.name === f.name &&
|
||||
pf.size === f.size &&
|
||||
pf.lastModified === f.lastModified,
|
||||
)
|
||||
) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
return dt.files;
|
||||
});
|
||||
}
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Box $padding={{ bottom: `${isDesktop ? 'base' : ''}` }}>
|
||||
<Box
|
||||
$flex={1}
|
||||
$radius="12px"
|
||||
$position="relative"
|
||||
$background="white"
|
||||
$css={`
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(fileUploadEnabled);
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDragActive(false);
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault();
|
||||
// File handling is now done by global handler
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Box $padding={{ bottom: `${isDesktop ? 'base' : ''}` }}>
|
||||
<Box
|
||||
$flex={1}
|
||||
$radius="12px"
|
||||
$position="relative"
|
||||
$background="white"
|
||||
$css={`
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
|
||||
border-radius: 12px;
|
||||
border: ${
|
||||
isDragActive
|
||||
? '2px dashed var(--c--theme--colors--primary-400)'
|
||||
: '1px solid var(--c--theme--colors--greyscale-200)'
|
||||
};
|
||||
border: 1px solid var(--c--theme--colors--greyscale-200);
|
||||
position: relative;
|
||||
background: white;
|
||||
transition: all 0.2s ease;
|
||||
`}
|
||||
>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={input ?? ''}
|
||||
name="inputchat-textarea"
|
||||
onChange={(e) => {
|
||||
handleInputChange(e);
|
||||
const textarea = e.target as HTMLTextAreaElement;
|
||||
textarea.style.height = 'auto';
|
||||
const newHeight = Math.min(textarea.scrollHeight, 200);
|
||||
textarea.style.height = `${newHeight}px`;
|
||||
textarea.focus();
|
||||
}}
|
||||
disabled={isInputDisabled}
|
||||
rows={1}
|
||||
style={{
|
||||
padding: '1rem 1.5rem',
|
||||
background: 'transparent',
|
||||
outline: 'none',
|
||||
fontSize: '1rem',
|
||||
border: 'none',
|
||||
resize: 'none',
|
||||
fontFamily: 'inherit',
|
||||
minHeight: '64px',
|
||||
maxHeight: '200px',
|
||||
overflowY: 'auto',
|
||||
transition: 'all 0.2s ease',
|
||||
borderRadius: '12px',
|
||||
color: 'var(--c--theme--colors--greyscale-800)',
|
||||
lineHeight: '1.5',
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
>
|
||||
{isDragActive && (
|
||||
<Box
|
||||
$position="absolute"
|
||||
$align="center"
|
||||
$direction="row"
|
||||
$justify="center"
|
||||
$gap="1rem"
|
||||
$css={`
|
||||
top: -1px; left: -1px;
|
||||
border-radius: 12px;
|
||||
z-index: 1001;
|
||||
background-color: #EDF0FF;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: 2px solid #90A7FF;
|
||||
box-shadow: 0 0 64px 0 rgba(62, 93, 231, 0.25);
|
||||
`}
|
||||
>
|
||||
<FilesIcon />
|
||||
<Box>
|
||||
<Text $weight="700" $color="#223E9E">
|
||||
{t('Add file')}
|
||||
</Text>
|
||||
<Text $weight="400" $color="#223E9E">
|
||||
{t('To add a file to the conversation, drop it here.')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={input ?? ''}
|
||||
name="inputchat-textarea"
|
||||
onChange={(e) => {
|
||||
handleInputChange(e);
|
||||
const textarea = e.target as HTMLTextAreaElement;
|
||||
textarea.style.height = '0';
|
||||
e.currentTarget.form?.requestSubmit?.();
|
||||
textarea.style.height = 'auto';
|
||||
const newHeight = Math.min(textarea.scrollHeight, 200);
|
||||
textarea.style.height = `${newHeight}px`;
|
||||
textarea.focus();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
disabled={isInputDisabled}
|
||||
rows={1}
|
||||
style={{
|
||||
padding: '1rem 1.5rem',
|
||||
background: 'transparent',
|
||||
outline: 'none',
|
||||
fontSize: '1rem',
|
||||
border: 'none',
|
||||
resize: 'none',
|
||||
fontFamily: 'inherit',
|
||||
minHeight: '64px',
|
||||
maxHeight: '200px',
|
||||
overflowY: 'auto',
|
||||
transition: 'all 0.2s ease',
|
||||
borderRadius: '12px',
|
||||
color: 'var(--c--theme--colors--greyscale-800)',
|
||||
lineHeight: '1.5',
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
const textarea = e.target as HTMLTextAreaElement;
|
||||
textarea.style.height = '0';
|
||||
e.currentTarget.form?.requestSubmit?.();
|
||||
textarea.focus();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{!input && (
|
||||
<Box
|
||||
$css={`
|
||||
{!input && (
|
||||
<Box
|
||||
$css={`
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1.5rem;
|
||||
@@ -291,130 +379,132 @@ export const InputChat = ({
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
$css={`
|
||||
>
|
||||
<Box
|
||||
$css={`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: ${(suggestions.length + 1) * 100}%;
|
||||
transform: translateY(-${currentSuggestionIndex * (100 / (suggestions.length + 1))}%);
|
||||
transition: ${isResetting ? 'none' : 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)'};
|
||||
`}
|
||||
>
|
||||
{[...suggestions, suggestions[0]].map((suggestion, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
$css={`
|
||||
>
|
||||
{[...suggestions, suggestions[0]].map(
|
||||
(suggestion, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
$css={`
|
||||
height: calc(100% / ${suggestions.length + 1});
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
`}
|
||||
>
|
||||
{suggestion}
|
||||
</Box>
|
||||
))}
|
||||
>
|
||||
{suggestion}
|
||||
</Box>
|
||||
),
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
|
||||
<input
|
||||
accept={conf?.chat_upload_accept}
|
||||
type="file"
|
||||
multiple
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const fileList = e.target.files;
|
||||
if (!fileList) {
|
||||
return;
|
||||
}
|
||||
setFiles((prev) => {
|
||||
const dt = new DataTransfer();
|
||||
if (prev) {
|
||||
Array.from(prev).forEach((f: File) => dt.items.add(f));
|
||||
<input
|
||||
accept={conf?.chat_upload_accept}
|
||||
type="file"
|
||||
multiple
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const fileList = e.target.files;
|
||||
if (!fileList) {
|
||||
return;
|
||||
}
|
||||
Array.from(fileList).forEach((f: File) => {
|
||||
if (
|
||||
!Array.from(prev || []).some(
|
||||
(pf) =>
|
||||
pf.name === f.name &&
|
||||
pf.size === f.size &&
|
||||
pf.lastModified === f.lastModified,
|
||||
)
|
||||
) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
return dt.files;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/*Aperçu des fichiers*/}
|
||||
{files && files.length > 0 && (
|
||||
<Box
|
||||
$margin={{ horizontal: '0', bottom: 'xs', top: 'xs' }}
|
||||
$padding={{ horizontal: 'base' }}
|
||||
>
|
||||
<AttachmentList
|
||||
attachments={Array.from(files).map((file) => ({
|
||||
name: file.name,
|
||||
contentType: file.type,
|
||||
url: URL.createObjectURL(file),
|
||||
}))}
|
||||
onRemove={(index) => {
|
||||
setFiles((prev) => {
|
||||
const dt = new DataTransfer();
|
||||
Array.from(files).forEach((f, i) => {
|
||||
if (i !== index) {
|
||||
if (prev) {
|
||||
Array.from(prev).forEach((f: File) => dt.items.add(f));
|
||||
}
|
||||
Array.from(fileList).forEach((f: File) => {
|
||||
if (
|
||||
!Array.from(prev || []).some(
|
||||
(pf) =>
|
||||
pf.name === f.name &&
|
||||
pf.size === f.size &&
|
||||
pf.lastModified === f.lastModified,
|
||||
)
|
||||
) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
setFiles(dt.files.length > 0 ? dt.files : null);
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap="sm"
|
||||
$padding={{ bottom: 'base' }}
|
||||
$align="space-between"
|
||||
>
|
||||
<Box
|
||||
$flex="1"
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'base' }}
|
||||
$gap="xs"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="button"
|
||||
disabled={!fileUploadEnabled || isUploadingFiles}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
aria-label={t('Add attach file')}
|
||||
className="c__button--neutral attach-file-button"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="attach_file"
|
||||
$theme="greyscale"
|
||||
$variation="550"
|
||||
$size={`${isMobile ? '24px' : '16px'}`}
|
||||
/>
|
||||
}
|
||||
return dt.files;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/*Aperçu des fichiers*/}
|
||||
{files && files.length > 0 && (
|
||||
<Box
|
||||
$margin={{ horizontal: '0', bottom: 'xs', top: 'xs' }}
|
||||
$padding={{ horizontal: 'base' }}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Text $theme="greyscale" $variation="550" $weight="500">
|
||||
{t('Attach file')}
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
<AttachmentList
|
||||
attachments={Array.from(files).map((file) => ({
|
||||
name: file.name,
|
||||
contentType: file.type,
|
||||
url: URL.createObjectURL(file),
|
||||
}))}
|
||||
onRemove={(index) => {
|
||||
const dt = new DataTransfer();
|
||||
Array.from(files).forEach((f, i) => {
|
||||
if (i !== index) {
|
||||
dt.items.add(f);
|
||||
}
|
||||
});
|
||||
setFiles(dt.files.length > 0 ? dt.files : null);
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap="sm"
|
||||
$padding={{ bottom: 'base' }}
|
||||
$align="space-between"
|
||||
>
|
||||
<Box
|
||||
$flex="1"
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'base' }}
|
||||
$gap="xs"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="button"
|
||||
disabled={!fileUploadEnabled || isUploadingFiles}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
aria-label={t('Add attach file')}
|
||||
className="c__button--neutral attach-file-button"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="attach_file"
|
||||
$theme="greyscale"
|
||||
$variation="550"
|
||||
$size={`${isMobile ? '24px' : '16px'}`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Text $theme="greyscale" $variation="550" $weight="500">
|
||||
{t('Attach file')}
|
||||
</Text>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{onToggleWebSearch && (
|
||||
<Box
|
||||
$margin={{ left: '4px' }}
|
||||
$css={`
|
||||
{onToggleWebSearch && (
|
||||
<Box
|
||||
$margin={{ left: '4px' }}
|
||||
$css={`
|
||||
${
|
||||
isMobile
|
||||
? `
|
||||
@@ -434,96 +524,101 @@ export const InputChat = ({
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="button"
|
||||
disabled={!webSearchEnabled || isUploadingFiles}
|
||||
onClick={() => {
|
||||
onToggleWebSearch();
|
||||
textareaRef.current?.focus();
|
||||
}}
|
||||
aria-label={t('Research on the web')}
|
||||
className="c__button--neutral research-web-button"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="language"
|
||||
$theme="greyscale"
|
||||
$variation="550"
|
||||
$css={`
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="button"
|
||||
disabled={!webSearchEnabled || isUploadingFiles}
|
||||
onClick={() => {
|
||||
onToggleWebSearch();
|
||||
textareaRef.current?.focus();
|
||||
}}
|
||||
aria-label={t('Research on the web')}
|
||||
className="c__button--neutral research-web-button"
|
||||
icon={
|
||||
<Icon
|
||||
iconName="language"
|
||||
$theme="greyscale"
|
||||
$variation="550"
|
||||
$css={`
|
||||
color: ${forceWebSearch ? 'var(--c--theme--colors--primary-600) !important' : 'var(--c--theme--colors--greyscale-600)'}
|
||||
`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Text
|
||||
$theme={forceWebSearch ? 'primary' : 'greyscale'}
|
||||
$variation="550"
|
||||
>
|
||||
{t('Research on the web')}
|
||||
</Text>
|
||||
)}
|
||||
{isMobile && forceWebSearch && (
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="space-between"
|
||||
$gap="xs"
|
||||
$css={`
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Text
|
||||
$theme={forceWebSearch ? 'primary' : 'greyscale'}
|
||||
$variation="550"
|
||||
>
|
||||
{t('Research on the web')}
|
||||
</Text>
|
||||
)}
|
||||
{isMobile && forceWebSearch && (
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="space-between"
|
||||
$gap="xs"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
`}
|
||||
>
|
||||
<Text
|
||||
$theme="primary"
|
||||
$weight="500"
|
||||
$css={`
|
||||
>
|
||||
<Text
|
||||
$theme="primary"
|
||||
$weight="500"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
{t('Web')}
|
||||
</Text>
|
||||
<Icon
|
||||
iconName="close"
|
||||
$variation="text"
|
||||
$theme="primary"
|
||||
$size="md"
|
||||
$css={`
|
||||
>
|
||||
{t('Web')}
|
||||
</Text>
|
||||
<Icon
|
||||
iconName="close"
|
||||
$variation="text"
|
||||
$theme="primary"
|
||||
$size="md"
|
||||
$css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
padding-left: 4px;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box $direction="row" $padding={{ horizontal: 'base' }} $gap="xs">
|
||||
<Box $padding={{ horizontal: 'xs' }}>
|
||||
{onModelSelect && (
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel || null}
|
||||
onModelSelect={onModelSelect}
|
||||
/>
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'base' }}
|
||||
$gap="xs"
|
||||
>
|
||||
<Box $padding={{ horizontal: 'xs' }}>
|
||||
{onModelSelect && (
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel || null}
|
||||
onModelSelect={onModelSelect}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<SendButton
|
||||
status={status}
|
||||
disabled={!input || !input.trim() || isUploadingFiles}
|
||||
onClick={onStop}
|
||||
/>
|
||||
<SendButton
|
||||
status={status}
|
||||
disabled={!input || !input.trim() || isUploadingFiles}
|
||||
onClick={onStop}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user