feat: update version to 1.4.6 and enhance code block component with download and copy sticky support
This commit is contained in:
		
							parent
							
								
									a46156847d
								
							
						
					
					
						commit
						40698e02d7
					
				| @ -42,10 +42,36 @@ export const CodeBlock: FC<Props> = ({ language, value }) => { | |||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <div className="code relative text-base font-sans codeblock bg-zinc-950 rounded-md overflow-hidden"> |       <div className="not-prose"> | ||||||
|         <div className="flex bg-gray-800 items-center justify-between py-1.5 px-4"> |         <div className=" [&_div+div]:!mt-0 my-4 bg-zinc-950 rounded-xl"> | ||||||
|           <span className="text-xs lowercase text-gray-200">{language}</span> |           <div className="flex flex-row px-4 py-2 rounded-t-xl  bg-gray-800 "> | ||||||
|  |             <span className="font-mono text-xs">{language || "text"}</span> | ||||||
|  |           </div> | ||||||
|  |           <div className="sticky top-9 md:top-[5.75rem]"> | ||||||
|  |             <div className="absolute bottom-0 right-2 flex h-9 items-center"> | ||||||
|  |               <Tooltip title={t("downloadCode")}> | ||||||
|  |                 <button | ||||||
|  |                   onClick={handleDownload} | ||||||
|  |                   className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none"> | ||||||
|  |                   <DownloadIcon className="size-4" /> | ||||||
|  |                 </button> | ||||||
|  |               </Tooltip> | ||||||
|  |               <Tooltip title={t("copyToClipboard")}> | ||||||
|  |                 <button | ||||||
|  |                   onClick={handleCopy} | ||||||
|  |                   className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-200 hover:bg-gray-700 hover:text-gray-100 focus:outline-none"> | ||||||
|  |                   {!isBtnPressed ? ( | ||||||
|  |                     <ClipboardIcon className="size-4" /> | ||||||
|  |                   ) : ( | ||||||
|  |                     <CheckIcon className="size-4 text-green-400" /> | ||||||
|  |                   )} | ||||||
|  |                 </button> | ||||||
|  |               </Tooltip> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
| 
 | 
 | ||||||
|  |           {/* <div className="flex sticky bg-gray-800 items-center justify-between py-1.5 px-4"> | ||||||
|  |           <span className="text-xs lowercase text-gray-200">{language}</span> | ||||||
|           <div className="flex items-center gap-2"> |           <div className="flex items-center gap-2"> | ||||||
|             <Tooltip title={t("downloadCode")}> |             <Tooltip title={t("downloadCode")}> | ||||||
|               <button |               <button | ||||||
| @ -66,28 +92,29 @@ export const CodeBlock: FC<Props> = ({ language, value }) => { | |||||||
|               </button> |               </button> | ||||||
|             </Tooltip> |             </Tooltip> | ||||||
|           </div> |           </div> | ||||||
|  |         </div> */} | ||||||
|  |           <SyntaxHighlighter | ||||||
|  |             language={language} | ||||||
|  |             style={coldarkDark} | ||||||
|  |             PreTag="div" | ||||||
|  |             customStyle={{ | ||||||
|  |               margin: 0, | ||||||
|  |               width: "100%", | ||||||
|  |               background: "transparent", | ||||||
|  |               padding: "1.5rem 1rem" | ||||||
|  |             }} | ||||||
|  |             lineNumberStyle={{ | ||||||
|  |               userSelect: "none" | ||||||
|  |             }} | ||||||
|  |             codeTagProps={{ | ||||||
|  |               style: { | ||||||
|  |                 fontSize: "0.9rem", | ||||||
|  |                 fontFamily: "var(--font-mono)" | ||||||
|  |               } | ||||||
|  |             }}> | ||||||
|  |             {value} | ||||||
|  |           </SyntaxHighlighter> | ||||||
|         </div> |         </div> | ||||||
|         <SyntaxHighlighter |  | ||||||
|           language={language} |  | ||||||
|           style={coldarkDark} |  | ||||||
|           PreTag="div" |  | ||||||
|           customStyle={{ |  | ||||||
|             margin: 0, |  | ||||||
|             width: "100%", |  | ||||||
|             background: "transparent", |  | ||||||
|             padding: "1.5rem 1rem" |  | ||||||
|           }} |  | ||||||
|           lineNumberStyle={{ |  | ||||||
|             userSelect: "none" |  | ||||||
|           }} |  | ||||||
|           codeTagProps={{ |  | ||||||
|             style: { |  | ||||||
|               fontSize: "0.9rem", |  | ||||||
|               fontFamily: "var(--font-mono)" |  | ||||||
|             } |  | ||||||
|           }}> |  | ||||||
|           {value} |  | ||||||
|         </SyntaxHighlighter> |  | ||||||
|       </div> |       </div> | ||||||
|       {previewVisible && ( |       {previewVisible && ( | ||||||
|         <Modal |         <Modal | ||||||
|  | |||||||
| @ -25,6 +25,9 @@ function Markdown({ | |||||||
|         remarkPlugins={[remarkGfm, remarkMath]} |         remarkPlugins={[remarkGfm, remarkMath]} | ||||||
|         rehypePlugins={[rehypeKatex]} |         rehypePlugins={[rehypeKatex]} | ||||||
|         components={{ |         components={{ | ||||||
|  |           pre({ children }) { | ||||||
|  |             return children | ||||||
|  |           }, | ||||||
|           code({ node, inline, className, children, ...props }) { |           code({ node, inline, className, children, ...props }) { | ||||||
|             const match = /language-(\w+)/.exec(className || "") |             const match = /language-(\w+)/.exec(className || "") | ||||||
|             return !inline ? ( |             return !inline ? ( | ||||||
|  | |||||||
| @ -54,244 +54,247 @@ export const PlaygroundMessage = (props: Props) => { | |||||||
|   return ( |   return ( | ||||||
|     <div className="group relative flex w-full max-w-3xl flex-col items-end justify-center pb-2 md:px-4 lg:w-4/5 text-gray-800 dark:text-gray-100"> |     <div className="group relative flex w-full max-w-3xl flex-col items-end justify-center pb-2 md:px-4 lg:w-4/5 text-gray-800 dark:text-gray-100"> | ||||||
|       {/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */} |       {/* <div className="text-base md:max-w-2xl lg:max-w-xl xl:max-w-3xl flex lg:px-0 m-auto w-full"> */} | ||||||
|         <div className="flex flex-row gap-4 md:gap-6 p-4 m-auto w-full"> |       <div className="flex flex-row gap-4 md:gap-6 my-2 m-auto w-full"> | ||||||
|           <div className="w-8 flex flex-col relative items-end"> |         <div className="w-8 flex flex-col relative items-end"> | ||||||
|             <div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center  text-opacity-100r"> |           <div className="relative h-7 w-7 p-1 rounded-sm text-white flex items-center justify-center  text-opacity-100r"> | ||||||
|               {props.isBot ? ( |             {props.isBot ? ( | ||||||
|                 !props.botAvatar ? ( |               !props.botAvatar ? ( | ||||||
|                   <div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div> |                 <div className="absolute h-8 w-8 rounded-full bg-gradient-to-r from-green-300 to-purple-400"></div> | ||||||
|                 ) : ( |  | ||||||
|                   props.botAvatar |  | ||||||
|                 ) |  | ||||||
|               ) : !props.userAvatar ? ( |  | ||||||
|                 <div className="absolute h-8 w-8 rounded-full from-blue-400 to-blue-600 bg-gradient-to-r"></div> |  | ||||||
|               ) : ( |               ) : ( | ||||||
|                 props.userAvatar |                 props.botAvatar | ||||||
|               )} |               ) | ||||||
|             </div> |             ) : !props.userAvatar ? ( | ||||||
|           </div> |               <div className="absolute h-8 w-8 rounded-full from-blue-400 to-blue-600 bg-gradient-to-r"></div> | ||||||
|           <div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]"> |             ) : ( | ||||||
|             <span className="text-xs font-bold text-gray-800 dark:text-white"> |               props.userAvatar | ||||||
|               {props.isBot |  | ||||||
|                 ? props.name === "chrome::gemini-nano::page-assist" |  | ||||||
|                   ? "Gemini Nano" |  | ||||||
|                   : removeModelSuffix( |  | ||||||
|                       props.name?.replaceAll(/accounts\/[^\/]+\/models\//g, "") |  | ||||||
|                     ) |  | ||||||
|                 : "You"} |  | ||||||
|             </span> |  | ||||||
| 
 |  | ||||||
|             {props.isBot && |  | ||||||
|             props.isSearchingInternet && |  | ||||||
|             props.currentMessageIndex === props.totalMessages - 1 ? ( |  | ||||||
|               <WebSearch /> |  | ||||||
|             ) : null} |  | ||||||
|             <div> |  | ||||||
|               {props?.message_type && ( |  | ||||||
|                 <Tag color={tagColors[props?.message_type] || "default"}> |  | ||||||
|                   {t(`copilot.${props?.message_type}`)} |  | ||||||
|                 </Tag> |  | ||||||
|               )} |  | ||||||
|             </div> |  | ||||||
|             <div className="flex flex-grow flex-col"> |  | ||||||
|               {!editMode ? ( |  | ||||||
|                 props.isBot ? ( |  | ||||||
|                   <> |  | ||||||
|                     {parseReasoning(props.message).map((e, i) => { |  | ||||||
|                       if (e.type === "reasoning") { |  | ||||||
|                         return ( |  | ||||||
|                           <Collapse |  | ||||||
|                             key={i} |  | ||||||
|                             className="border-none !mb-3" |  | ||||||
|                             items={[ |  | ||||||
|                               { |  | ||||||
|                                 key: "reasoning", |  | ||||||
|                                 label: |  | ||||||
|                                   props.isStreaming && e?.reasoning_running ? ( |  | ||||||
|                                     <div className="flex items-center gap-2"> |  | ||||||
|                                       <span className="italic"> |  | ||||||
|                                         {t("reasoning.thinking")} |  | ||||||
|                                       </span> |  | ||||||
|                                     </div> |  | ||||||
|                                   ) : ( |  | ||||||
|                                     t("reasoning.thought", { |  | ||||||
|                                       time: humanizeMilliseconds( |  | ||||||
|                                         props.reasoningTimeTaken |  | ||||||
|                                       ) |  | ||||||
|                                     }) |  | ||||||
|                                   ), |  | ||||||
|                                 children: <Markdown message={e.content} /> |  | ||||||
|                               } |  | ||||||
|                             ]} |  | ||||||
|                           /> |  | ||||||
|                         ) |  | ||||||
|                       } |  | ||||||
| 
 |  | ||||||
|                       return <Markdown key={i} message={e.content} /> |  | ||||||
|                     })} |  | ||||||
|                   </> |  | ||||||
|                 ) : ( |  | ||||||
|                   <p |  | ||||||
|                     className={`prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${ |  | ||||||
|                       props.message_type && |  | ||||||
|                       "italic text-gray-500 dark:text-gray-400 text-sm" |  | ||||||
|                     }`}>
 |  | ||||||
|                     {props.message} |  | ||||||
|                   </p> |  | ||||||
|                 ) |  | ||||||
|               ) : ( |  | ||||||
|                 <EditMessageForm |  | ||||||
|                   value={props.message} |  | ||||||
|                   onSumbit={props.onEditFormSubmit} |  | ||||||
|                   onClose={() => setEditMode(false)} |  | ||||||
|                   isBot={props.isBot} |  | ||||||
|                 /> |  | ||||||
|               )} |  | ||||||
|             </div> |  | ||||||
|             {/* source if available */} |  | ||||||
|             {props.images && |  | ||||||
|               props.images.filter((img) => img.length > 0).length > 0 && ( |  | ||||||
|                 <div className="flex md:max-w-2xl lg:max-w-xl xl:max-w-3xl mt-4 m-auto w-full"> |  | ||||||
|                   {props.images |  | ||||||
|                     .filter((image) => image.length > 0) |  | ||||||
|                     .map((image, index) => ( |  | ||||||
|                       <Image |  | ||||||
|                         key={index} |  | ||||||
|                         src={image} |  | ||||||
|                         alt="Uploaded Image" |  | ||||||
|                         width={180} |  | ||||||
|                         className="rounded-md relative" |  | ||||||
|                       /> |  | ||||||
|                     ))} |  | ||||||
|                 </div> |  | ||||||
|               )} |  | ||||||
| 
 |  | ||||||
|             {props.isBot && props?.sources && props?.sources.length > 0 && ( |  | ||||||
|               <Collapse |  | ||||||
|                 className="mt-6" |  | ||||||
|                 ghost |  | ||||||
|                 items={[ |  | ||||||
|                   { |  | ||||||
|                     key: "1", |  | ||||||
|                     label: ( |  | ||||||
|                       <div className="italic text-gray-500 dark:text-gray-400"> |  | ||||||
|                         {t("citations")} |  | ||||||
|                       </div> |  | ||||||
|                     ), |  | ||||||
|                     children: ( |  | ||||||
|                       <div className="mb-3 flex flex-wrap gap-2"> |  | ||||||
|                         {props?.sources?.map((source, index) => ( |  | ||||||
|                           <MessageSource |  | ||||||
|                             onSourceClick={props.onSourceClick} |  | ||||||
|                             key={index} |  | ||||||
|                             source={source} |  | ||||||
|                           /> |  | ||||||
|                         ))} |  | ||||||
|                       </div> |  | ||||||
|                     ) |  | ||||||
|                   } |  | ||||||
|                 ]} |  | ||||||
|               /> |  | ||||||
|             )} |  | ||||||
|             {!props.isProcessing && !editMode && ( |  | ||||||
|               <div |  | ||||||
|                 className={`space-x-2 gap-2 mt-3 flex ${ |  | ||||||
|                   props.currentMessageIndex !== props.totalMessages - 1 |  | ||||||
|                     ? //  there is few style issue so i am commenting this out for v1.4.5 release
 |  | ||||||
|                       // next release we will fix this
 |  | ||||||
|                       "invisible group-hover:visible" |  | ||||||
|                     : // ? "hidden group-hover:flex"
 |  | ||||||
|                       "" |  | ||||||
|                   // : "flex"
 |  | ||||||
|                 }`}>
 |  | ||||||
|                 {props.isTTSEnabled && ( |  | ||||||
|                   <Tooltip title={t("tts")}> |  | ||||||
|                     <button |  | ||||||
|                       aria-label={t("tts")} |  | ||||||
|                       onClick={() => { |  | ||||||
|                         if (isSpeaking) { |  | ||||||
|                           cancel() |  | ||||||
|                         } else { |  | ||||||
|                           speak({ |  | ||||||
|                             utterance: removeReasoning(props.message) |  | ||||||
|                           }) |  | ||||||
|                         } |  | ||||||
|                       }} |  | ||||||
|                       className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |  | ||||||
|                       {!isSpeaking ? ( |  | ||||||
|                         <PlayIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> |  | ||||||
|                       ) : ( |  | ||||||
|                         <Square className="w-3 h-3 text-red-400 group-hover:text-red-500" /> |  | ||||||
|                       )} |  | ||||||
|                     </button> |  | ||||||
|                   </Tooltip> |  | ||||||
|                 )} |  | ||||||
|                 {props.isBot && ( |  | ||||||
|                   <> |  | ||||||
|                     {!props.hideCopy && ( |  | ||||||
|                       <Tooltip title={t("copyToClipboard")}> |  | ||||||
|                         <button |  | ||||||
|                           aria-label={t("copyToClipboard")} |  | ||||||
|                           onClick={() => { |  | ||||||
|                             navigator.clipboard.writeText(props.message) |  | ||||||
|                             setIsBtnPressed(true) |  | ||||||
|                             setTimeout(() => { |  | ||||||
|                               setIsBtnPressed(false) |  | ||||||
|                             }, 2000) |  | ||||||
|                           }} |  | ||||||
|                           className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |  | ||||||
|                           {!isBtnPressed ? ( |  | ||||||
|                             <ClipboardIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> |  | ||||||
|                           ) : ( |  | ||||||
|                             <CheckIcon className="w-3 h-3 text-green-400 group-hover:text-green-500" /> |  | ||||||
|                           )} |  | ||||||
|                         </button> |  | ||||||
|                       </Tooltip> |  | ||||||
|                     )} |  | ||||||
| 
 |  | ||||||
|                     {props.generationInfo && ( |  | ||||||
|                       <Popover |  | ||||||
|                         content={ |  | ||||||
|                           <GenerationInfo |  | ||||||
|                             generationInfo={props.generationInfo} |  | ||||||
|                           /> |  | ||||||
|                         } |  | ||||||
|                         title={t("generationInfo")}> |  | ||||||
|                         <button |  | ||||||
|                           aria-label={t("generationInfo")} |  | ||||||
|                           className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |  | ||||||
|                           <InfoIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> |  | ||||||
|                         </button> |  | ||||||
|                       </Popover> |  | ||||||
|                     )} |  | ||||||
| 
 |  | ||||||
|                     {!props.hideEditAndRegenerate && |  | ||||||
|                       props.currentMessageIndex === props.totalMessages - 1 && ( |  | ||||||
|                         <Tooltip title={t("regenerate")}> |  | ||||||
|                           <button |  | ||||||
|                             aria-label={t("regenerate")} |  | ||||||
|                             onClick={props.onRengerate} |  | ||||||
|                             className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |  | ||||||
|                             <RotateCcw className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> |  | ||||||
|                           </button> |  | ||||||
|                         </Tooltip> |  | ||||||
|                       )} |  | ||||||
|                   </> |  | ||||||
|                 )} |  | ||||||
|                 {!props.hideEditAndRegenerate && ( |  | ||||||
|                   <Tooltip title={t("edit")}> |  | ||||||
|                     <button |  | ||||||
|                       onClick={() => setEditMode(true)} |  | ||||||
|                       aria-label={t("edit")} |  | ||||||
|                       className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> |  | ||||||
|                       <Pen className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> |  | ||||||
|                     </button> |  | ||||||
|                   </Tooltip> |  | ||||||
|                 )} |  | ||||||
|               </div> |  | ||||||
|             )} |             )} | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|  |         <div className="flex w-[calc(100%-50px)] flex-col gap-2 lg:w-[calc(100%-115px)]"> | ||||||
|  |           <span className="text-xs font-bold text-gray-800 dark:text-white"> | ||||||
|  |             {props.isBot | ||||||
|  |               ? props.name === "chrome::gemini-nano::page-assist" | ||||||
|  |                 ? "Gemini Nano" | ||||||
|  |                 : removeModelSuffix( | ||||||
|  |                     props.name?.replaceAll(/accounts\/[^\/]+\/models\//g, "") | ||||||
|  |                   ) | ||||||
|  |               : "You"} | ||||||
|  |           </span> | ||||||
|  | 
 | ||||||
|  |           {props.isBot && | ||||||
|  |           props.isSearchingInternet && | ||||||
|  |           props.currentMessageIndex === props.totalMessages - 1 ? ( | ||||||
|  |             <WebSearch /> | ||||||
|  |           ) : null} | ||||||
|  |           <div> | ||||||
|  |             {props?.message_type && ( | ||||||
|  |               <Tag color={tagColors[props?.message_type] || "default"}> | ||||||
|  |                 {t(`copilot.${props?.message_type}`)} | ||||||
|  |               </Tag> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |           <div className="flex flex-grow flex-col"> | ||||||
|  |             {!editMode ? ( | ||||||
|  |               props.isBot ? ( | ||||||
|  |                 <> | ||||||
|  |                   {parseReasoning(props.message).map((e, i) => { | ||||||
|  |                     if (e.type === "reasoning") { | ||||||
|  |                       return ( | ||||||
|  |                         <Collapse | ||||||
|  |                           key={i} | ||||||
|  |                           className="border-none !mb-3" | ||||||
|  |                           items={[ | ||||||
|  |                             { | ||||||
|  |                               key: "reasoning", | ||||||
|  |                               label: | ||||||
|  |                                 props.isStreaming && e?.reasoning_running ? ( | ||||||
|  |                                   <div className="flex items-center gap-2"> | ||||||
|  |                                     <span className="italic"> | ||||||
|  |                                       {t("reasoning.thinking")} | ||||||
|  |                                     </span> | ||||||
|  |                                   </div> | ||||||
|  |                                 ) : ( | ||||||
|  |                                   t("reasoning.thought", { | ||||||
|  |                                     time: humanizeMilliseconds( | ||||||
|  |                                       props.reasoningTimeTaken | ||||||
|  |                                     ) | ||||||
|  |                                   }) | ||||||
|  |                                 ), | ||||||
|  |                               children: <Markdown message={e.content} /> | ||||||
|  |                             } | ||||||
|  |                           ]} | ||||||
|  |                         /> | ||||||
|  |                       ) | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return <Markdown key={i} message={e.content} /> | ||||||
|  |                   })} | ||||||
|  |                 </> | ||||||
|  |               ) : ( | ||||||
|  |                 <p | ||||||
|  |                   className={`prose dark:prose-invert whitespace-pre-line prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark ${ | ||||||
|  |                     props.message_type && | ||||||
|  |                     "italic text-gray-500 dark:text-gray-400 text-sm" | ||||||
|  |                   }`}>
 | ||||||
|  |                   {props.message} | ||||||
|  |                 </p> | ||||||
|  |               ) | ||||||
|  |             ) : ( | ||||||
|  |               <EditMessageForm | ||||||
|  |                 value={props.message} | ||||||
|  |                 onSumbit={props.onEditFormSubmit} | ||||||
|  |                 onClose={() => setEditMode(false)} | ||||||
|  |                 isBot={props.isBot} | ||||||
|  |               /> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|  |           {/* source if available */} | ||||||
|  |           {props.images && | ||||||
|  |             props.images.filter((img) => img.length > 0).length > 0 && ( | ||||||
|  |               <div> | ||||||
|  |                 {props.images | ||||||
|  |                   .filter((image) => image.length > 0) | ||||||
|  |                   .map((image, index) => ( | ||||||
|  |                     <Image | ||||||
|  |                       key={index} | ||||||
|  |                       src={image} | ||||||
|  |                       alt="Uploaded Image" | ||||||
|  |                       width={180} | ||||||
|  |                       className="rounded-md relative" | ||||||
|  |                     /> | ||||||
|  |                   ))} | ||||||
|  |               </div> | ||||||
|  |             )} | ||||||
|  | 
 | ||||||
|  |           {props.isBot && props?.sources && props?.sources.length > 0 && ( | ||||||
|  |             <Collapse | ||||||
|  |               className="mt-6" | ||||||
|  |               ghost | ||||||
|  |               items={[ | ||||||
|  |                 { | ||||||
|  |                   key: "1", | ||||||
|  |                   label: ( | ||||||
|  |                     <div className="italic text-gray-500 dark:text-gray-400"> | ||||||
|  |                       {t("citations")} | ||||||
|  |                     </div> | ||||||
|  |                   ), | ||||||
|  |                   children: ( | ||||||
|  |                     <div className="mb-3 flex flex-wrap gap-2"> | ||||||
|  |                       {props?.sources?.map((source, index) => ( | ||||||
|  |                         <MessageSource | ||||||
|  |                           onSourceClick={props.onSourceClick} | ||||||
|  |                           key={index} | ||||||
|  |                           source={source} | ||||||
|  |                         /> | ||||||
|  |                       ))} | ||||||
|  |                     </div> | ||||||
|  |                   ) | ||||||
|  |                 } | ||||||
|  |               ]} | ||||||
|  |             /> | ||||||
|  |           )} | ||||||
|  |           {!props.isProcessing && !editMode ? ( | ||||||
|  |             <div | ||||||
|  |               className={`space-x-2 gap-2 flex ${ | ||||||
|  |                 props.currentMessageIndex !== props.totalMessages - 1 | ||||||
|  |                   ? //  there is few style issue so i am commenting this out for v1.4.5 release
 | ||||||
|  |                     // next release we will fix this
 | ||||||
|  |                     "invisible group-hover:visible" | ||||||
|  |                   : // ? "hidden group-hover:flex"
 | ||||||
|  |                     "" | ||||||
|  |                 // : "flex"
 | ||||||
|  |               }`}>
 | ||||||
|  |               {props.isTTSEnabled && ( | ||||||
|  |                 <Tooltip title={t("tts")}> | ||||||
|  |                   <button | ||||||
|  |                     aria-label={t("tts")} | ||||||
|  |                     onClick={() => { | ||||||
|  |                       if (isSpeaking) { | ||||||
|  |                         cancel() | ||||||
|  |                       } else { | ||||||
|  |                         speak({ | ||||||
|  |                           utterance: removeReasoning(props.message) | ||||||
|  |                         }) | ||||||
|  |                       } | ||||||
|  |                     }} | ||||||
|  |                     className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> | ||||||
|  |                     {!isSpeaking ? ( | ||||||
|  |                       <PlayIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> | ||||||
|  |                     ) : ( | ||||||
|  |                       <Square className="w-3 h-3 text-red-400 group-hover:text-red-500" /> | ||||||
|  |                     )} | ||||||
|  |                   </button> | ||||||
|  |                 </Tooltip> | ||||||
|  |               )} | ||||||
|  |               {props.isBot && ( | ||||||
|  |                 <> | ||||||
|  |                   {!props.hideCopy && ( | ||||||
|  |                     <Tooltip title={t("copyToClipboard")}> | ||||||
|  |                       <button | ||||||
|  |                         aria-label={t("copyToClipboard")} | ||||||
|  |                         onClick={() => { | ||||||
|  |                           navigator.clipboard.writeText(props.message) | ||||||
|  |                           setIsBtnPressed(true) | ||||||
|  |                           setTimeout(() => { | ||||||
|  |                             setIsBtnPressed(false) | ||||||
|  |                           }, 2000) | ||||||
|  |                         }} | ||||||
|  |                         className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> | ||||||
|  |                         {!isBtnPressed ? ( | ||||||
|  |                           <ClipboardIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> | ||||||
|  |                         ) : ( | ||||||
|  |                           <CheckIcon className="w-3 h-3 text-green-400 group-hover:text-green-500" /> | ||||||
|  |                         )} | ||||||
|  |                       </button> | ||||||
|  |                     </Tooltip> | ||||||
|  |                   )} | ||||||
|  | 
 | ||||||
|  |                   {props.generationInfo && ( | ||||||
|  |                     <Popover | ||||||
|  |                       content={ | ||||||
|  |                         <GenerationInfo generationInfo={props.generationInfo} /> | ||||||
|  |                       } | ||||||
|  |                       title={t("generationInfo")}> | ||||||
|  |                       <button | ||||||
|  |                         aria-label={t("generationInfo")} | ||||||
|  |                         className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> | ||||||
|  |                         <InfoIcon className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> | ||||||
|  |                       </button> | ||||||
|  |                     </Popover> | ||||||
|  |                   )} | ||||||
|  | 
 | ||||||
|  |                   {!props.hideEditAndRegenerate && | ||||||
|  |                     props.currentMessageIndex === props.totalMessages - 1 && ( | ||||||
|  |                       <Tooltip title={t("regenerate")}> | ||||||
|  |                         <button | ||||||
|  |                           aria-label={t("regenerate")} | ||||||
|  |                           onClick={props.onRengerate} | ||||||
|  |                           className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> | ||||||
|  |                           <RotateCcw className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> | ||||||
|  |                         </button> | ||||||
|  |                       </Tooltip> | ||||||
|  |                     )} | ||||||
|  |                 </> | ||||||
|  |               )} | ||||||
|  |               {!props.hideEditAndRegenerate && ( | ||||||
|  |                 <Tooltip title={t("edit")}> | ||||||
|  |                   <button | ||||||
|  |                     onClick={() => setEditMode(true)} | ||||||
|  |                     aria-label={t("edit")} | ||||||
|  |                     className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"> | ||||||
|  |                     <Pen className="w-3 h-3 text-gray-400 group-hover:text-gray-500" /> | ||||||
|  |                   </button> | ||||||
|  |                 </Tooltip> | ||||||
|  |               )} | ||||||
|  |             </div> | ||||||
|  |           ) : ( | ||||||
|  |             // add invisible div to prevent layout shift
 | ||||||
|  |             <div className="invisible"> | ||||||
|  |               <div className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"></div> | ||||||
|  |             </div> | ||||||
|  |           )} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|       {/* </div> */} |       {/* </div> */} | ||||||
|     </div> |     </div> | ||||||
|   ) |   ) | ||||||
|  | |||||||
| @ -89,10 +89,9 @@ export const Header: React.FC<Props> = ({ | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className={`absolute top-0 z-10 w-full flex h-14 p-3  bg-gray-50 border-b  dark:bg-[#171717] dark:border-gray-600 ${ |       className={`absolute top-0 z-10 flex h-14 w-full flex-row items-center justify-center p-3 overflow-x-auto lg:overflow-x-visible bg-gray-50 border-b  dark:bg-[#171717] dark:border-gray-600 ${ | ||||||
|         temporaryChat && "!bg-gray-200 dark:!bg-black" |         temporaryChat && "!bg-gray-200 dark:!bg-black" | ||||||
|       }`}>
 |       }`}>
 | ||||||
|       <div className="flex gap-2 items-center"> |       <div className="flex gap-2 items-center"> | ||||||
| @ -210,12 +209,6 @@ export const Header: React.FC<Props> = ({ | |||||||
|       <div className="flex flex-1 justify-end px-4"> |       <div className="flex flex-1 justify-end px-4"> | ||||||
|         <div className="ml-4 flex items-center md:ml-6"> |         <div className="ml-4 flex items-center md:ml-6"> | ||||||
|           <div className="flex gap-4 items-center"> |           <div className="flex gap-4 items-center"> | ||||||
|             {/* {pathname === "/" && |  | ||||||
|               messages.length > 0 && |  | ||||||
|               !streaming && |  | ||||||
|               shareModeEnabled && ( |  | ||||||
|                 <ShareBtn historyId={historyId} messages={messages} /> |  | ||||||
|               )} */} |  | ||||||
|             {messages.length > 0 && !streaming && ( |             {messages.length > 0 && !streaming && ( | ||||||
|               <MoreOptions |               <MoreOptions | ||||||
|                 shareModeEnabled={shareModeEnabled} |                 shareModeEnabled={shareModeEnabled} | ||||||
| @ -247,7 +240,7 @@ export const Header: React.FC<Props> = ({ | |||||||
|                 <CogIcon className="w-6 h-6" /> |                 <CogIcon className="w-6 h-6" /> | ||||||
|               </NavLink> |               </NavLink> | ||||||
|             </Tooltip> |             </Tooltip> | ||||||
|           </div> |         </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -39,13 +39,16 @@ export default function OptionLayout({ | |||||||
|     <div className="flex h-full w-full"> |     <div className="flex h-full w-full"> | ||||||
|       <main className="relative h-dvh w-full"> |       <main className="relative h-dvh w-full"> | ||||||
|         <div className="relative z-10 w-full"> |         <div className="relative z-10 w-full"> | ||||||
|  |           <div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-24 overflow-hidden sm:h-20"> | ||||||
|  |             <div className="bgGradientMask bg-background h-dvh w-screen"></div> | ||||||
|  |           </div> | ||||||
|           <Header |           <Header | ||||||
|             setSidebarOpen={setSidebarOpen} |             setSidebarOpen={setSidebarOpen} | ||||||
|             setOpenModelSettings={setOpenModelSettings} |             setOpenModelSettings={setOpenModelSettings} | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|         {/* <div className="relative flex h-full flex-col items-center"> */} |         {/* <div className="relative flex h-full flex-col items-center"> */} | ||||||
|           {children} |         {children} | ||||||
|         {/* </div> */} |         {/* </div> */} | ||||||
|         <Drawer |         <Drawer | ||||||
|           title={ |           title={ | ||||||
|  | |||||||
| @ -20,9 +20,7 @@ export const NewChat: React.FC<Props> = ({ clearChat }) => { | |||||||
|         <label className="flex items-center gap-6 justify-between px-1 py-0.5 cursor-pointer w-full"> |         <label className="flex items-center gap-6 justify-between px-1 py-0.5 cursor-pointer w-full"> | ||||||
|           <div className="flex items-center gap-2"> |           <div className="flex items-center gap-2"> | ||||||
|             <TimerReset className="h-4 w-4 text-gray-600" /> |             <TimerReset className="h-4 w-4 text-gray-600" /> | ||||||
|             <span> |             <span>{t("temporaryChat")}</span> | ||||||
|                 {t("temporaryChat")} |  | ||||||
|             </span> |  | ||||||
|           </div> |           </div> | ||||||
|           <Switch |           <Switch | ||||||
|             checked={temporaryChat} |             checked={temporaryChat} | ||||||
| @ -44,12 +42,12 @@ export const NewChat: React.FC<Props> = ({ clearChat }) => { | |||||||
|       <button |       <button | ||||||
|         onClick={clearChat} |         onClick={clearChat} | ||||||
|         className="inline-flex dark:bg-transparent bg-white items-center rounded-s-lg rounded-e-none border dark:border-gray-700 bg-transparent px-3 py-2.5 pe-6 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white"> |         className="inline-flex dark:bg-transparent bg-white items-center rounded-s-lg rounded-e-none border dark:border-gray-700 bg-transparent px-3 py-2.5 pe-6 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white"> | ||||||
|         <SquarePen className="h-5 w-5" /> |         <SquarePen className="size-4 sm:size-5" /> | ||||||
|         <span className="truncate ms-3">{t("newChat")}</span> |         <span className="truncate ms-3 hidden sm:inline">{t("newChat")}</span> | ||||||
|       </button> |       </button>{" "} | ||||||
|       <Dropdown menu={{ items }} trigger={["click"]}> |       <Dropdown menu={{ items }} trigger={["click"]}> | ||||||
|         <button className="inline-flex dark:bg-transparent bg-white items-center rounded-lg border-s-0 rounded-s-none border dark:border-gray-700 bg-transparent px-3 py-2.5 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white"> |         <button className="inline-flex dark:bg-transparent bg-white items-center rounded-lg border-s-0 rounded-s-none border dark:border-gray-700 bg-transparent px-3 py-2.5 text-xs lg:text-sm font-medium leading-4 text-gray-800 dark:text-white disabled:opacity-50 ease-in-out transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white"> | ||||||
|           <MoreHorizontal className="h-5 w-5 text-gray-600 dark:text-gray-400" /> |           <MoreHorizontal className="size-4 sm:size-5 text-gray-600 dark:text-gray-400" /> | ||||||
|         </button> |         </button> | ||||||
|       </Dropdown> |       </Dropdown> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
| @ -145,7 +145,7 @@ export const Playground = () => { | |||||||
|           <div className="fixed bottom-36 z-20 left-0 right-0 flex justify-center"> |           <div className="fixed bottom-36 z-20 left-0 right-0 flex justify-center"> | ||||||
|             <button |             <button | ||||||
|               onClick={scrollToBottom} |               onClick={scrollToBottom} | ||||||
|               className="bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto"> |               className="bg-gray-50 shadow border border-gray-200 dark:border-none dark:bg-white/20 p-1.5 rounded-full pointer-events-auto"> | ||||||
|               <ChevronDown className="size-4 text-gray-600 dark:text-gray-300" /> |               <ChevronDown className="size-4 text-gray-600 dark:text-gray-300" /> | ||||||
|             </button> |             </button> | ||||||
|           </div> |           </div> | ||||||
|  | |||||||
| @ -205,39 +205,35 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex w-full flex-col items-center p-2 pt-1 sm:px-4 pb-4"> |     <div className="flex w-full flex-col items-center p-2 pt-1  pb-4"> | ||||||
|       <form className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base"> |       <form className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base"> | ||||||
|         <div className="relative flex w-full flex-row justify-center gap-2 lg:w-4/5"> |         <div className="relative flex w-full flex-row justify-center gap-2 lg:w-4/5"> | ||||||
|           <div |           <div | ||||||
|             className={` bg-gray-100  dark:bg-[#262626] relative w-full max-w-[45rem] p-1 backdrop-blur-lg duration-100 sm:rounded-3xl border rounded-2xl  dark:border-gray-600
 |             className={` bg-neutral-50  dark:bg-[#262626] relative w-full max-w-[48rem] p-1 backdrop-blur-lg duration-100 border border-gray-300 rounded-xl  dark:border-gray-600
 | ||||||
|             ${temporaryChat && "!bg-gray-200 dark:!bg-black "} |             ${temporaryChat ? "!bg-gray-200 dark:!bg-black " : ""} | ||||||
|             `}>
 |             `}>
 | ||||||
|             <div |             <div | ||||||
|               className={`h-full shadow relative ${ |               className={`border-b border-gray-200 dark:border-gray-600 relative ${ | ||||||
|                 form.values.image.length === 0 ? "hidden" : "block" |                 form.values.image.length === 0 ? "hidden" : "block" | ||||||
|               }`}>
 |               }`}>
 | ||||||
|               <div className="relative"> |               <button | ||||||
|                 <Image |                 type="button" | ||||||
|                   src={form.values.image} |                 onClick={() => { | ||||||
|                   alt="Uploaded Image" |                   form.setFieldValue("image", "") | ||||||
|                   width={180} |                 }} | ||||||
|                   preview={false} |                 className="absolute top-1 left-1 flex items-center justify-center z-10 bg-white dark:bg-[#262626] p-0.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100"> | ||||||
|                   className="rounded-md" |                 <X className="h-4 w-4" /> | ||||||
|                 /> |               </button>{" "} | ||||||
|                 <button |               <Image | ||||||
|                   onClick={() => { |                 src={form.values.image} | ||||||
|                     form.setFieldValue("image", "") |                 alt="Uploaded Image" | ||||||
|                   }} |                 preview={false} | ||||||
|                   className="flex items-center justify-center absolute top-0 m-2 bg-white  dark:bg-[#262626] p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100"> |                 className="rounded-md max-h-32" | ||||||
|                   <X className="h-5 w-5" /> |               /> | ||||||
|                 </button> |  | ||||||
|               </div> |  | ||||||
|             </div> |             </div> | ||||||
|             <div> |             <div> | ||||||
|               <div |               <div | ||||||
|                 className={`flex  bg-transparent ${ |                 className={`flex  bg-transparent `}> | ||||||
|                   temporaryChat && "!bg-gray-100 dark:!bg-black" |  | ||||||
|                 }`}>
 |  | ||||||
|                 <form |                 <form | ||||||
|                   onSubmit={form.onSubmit(async (value) => { |                   onSubmit={form.onSubmit(async (value) => { | ||||||
|                     stopListening() |                     stopListening() | ||||||
| @ -456,256 +452,4 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { | |||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
|   ) |   ) | ||||||
|   return ( |  | ||||||
|     <div className="flex w-full flex-col items-center p-2 pt-1 sm:px-4 sm:pb-4 md:pb-6"> |  | ||||||
|       <div className="relative z-10 flex w-full flex-col items-center justify-center gap-2 text-base"> |  | ||||||
|         <div className="relative flex w-full flex-row justify-center gap-2 lg:w-4/5"> |  | ||||||
|           <div |  | ||||||
|             className={` bg-gray-100  dark:bg-[#262626] border rounded-2xl  dark:border-gray-600
 |  | ||||||
|     ${temporaryChat && "!bg-gray-200 dark:!bg-black "} |  | ||||||
|     `}>
 |  | ||||||
|             <div |  | ||||||
|               className={`h-full shadow relative ${ |  | ||||||
|                 form.values.image.length === 0 ? "hidden" : "block" |  | ||||||
|               }`}>
 |  | ||||||
|               <div className="relative"> |  | ||||||
|                 <Image |  | ||||||
|                   src={form.values.image} |  | ||||||
|                   alt="Uploaded Image" |  | ||||||
|                   width={180} |  | ||||||
|                   preview={false} |  | ||||||
|                   className="rounded-md" |  | ||||||
|                 /> |  | ||||||
|                 <button |  | ||||||
|                   onClick={() => { |  | ||||||
|                     form.setFieldValue("image", "") |  | ||||||
|                   }} |  | ||||||
|                   className="flex items-center justify-center absolute top-0 m-2 bg-white  dark:bg-[#262626] p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100"> |  | ||||||
|                   <X className="h-5 w-5" /> |  | ||||||
|                 </button> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|             <div> |  | ||||||
|               <div |  | ||||||
|                 className={`flex  bg-white dark:bg-transparent ${ |  | ||||||
|                   temporaryChat && "!bg-gray-100 dark:!bg-black" |  | ||||||
|                 }`}>
 |  | ||||||
|                 <form |  | ||||||
|                   onSubmit={form.onSubmit(async (value) => { |  | ||||||
|                     stopListening() |  | ||||||
|                     if (!selectedModel || selectedModel.length === 0) { |  | ||||||
|                       form.setFieldError("message", t("formError.noModel")) |  | ||||||
|                       return |  | ||||||
|                     } |  | ||||||
|                     if (webSearch) { |  | ||||||
|                       const defaultEM = await defaultEmbeddingModelForRag() |  | ||||||
|                       if (!defaultEM) { |  | ||||||
|                         form.setFieldError( |  | ||||||
|                           "message", |  | ||||||
|                           t("formError.noEmbeddingModel") |  | ||||||
|                         ) |  | ||||||
|                         return |  | ||||||
|                       } |  | ||||||
|                     } |  | ||||||
|                     if ( |  | ||||||
|                       value.message.trim().length === 0 && |  | ||||||
|                       value.image.length === 0 |  | ||||||
|                     ) { |  | ||||||
|                       return |  | ||||||
|                     } |  | ||||||
|                     form.reset() |  | ||||||
|                     textAreaFocus() |  | ||||||
|                     await sendMessage({ |  | ||||||
|                       image: value.image, |  | ||||||
|                       message: value.message.trim() |  | ||||||
|                     }) |  | ||||||
|                   })} |  | ||||||
|                   className="shrink-0 flex-grow  flex flex-col items-center "> |  | ||||||
|                   <input |  | ||||||
|                     id="file-upload" |  | ||||||
|                     name="file-upload" |  | ||||||
|                     type="file" |  | ||||||
|                     className="sr-only" |  | ||||||
|                     ref={inputRef} |  | ||||||
|                     accept="image/*" |  | ||||||
|                     multiple={false} |  | ||||||
|                     onChange={onInputChange} |  | ||||||
|                   /> |  | ||||||
|                   <div className="w-full  flex flex-col dark:border-gray-600  p-2"> |  | ||||||
|                     <textarea |  | ||||||
|                       onCompositionStart={() => { |  | ||||||
|                         if (import.meta.env.BROWSER !== "firefox") { |  | ||||||
|                           setTyping(true) |  | ||||||
|                         } |  | ||||||
|                       }} |  | ||||||
|                       onCompositionEnd={() => { |  | ||||||
|                         if (import.meta.env.BROWSER !== "firefox") { |  | ||||||
|                           setTyping(false) |  | ||||||
|                         } |  | ||||||
|                       }} |  | ||||||
|                       onKeyDown={(e) => handleKeyDown(e)} |  | ||||||
|                       ref={textareaRef} |  | ||||||
|                       className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100" |  | ||||||
|                       onPaste={handlePaste} |  | ||||||
|                       rows={1} |  | ||||||
|                       style={{ minHeight: "35px" }} |  | ||||||
|                       tabIndex={0} |  | ||||||
|                       placeholder={t("form.textarea.placeholder")} |  | ||||||
|                       {...form.getInputProps("message")} |  | ||||||
|                     /> |  | ||||||
|                     <div className="mt-2 flex justify-between items-center"> |  | ||||||
|                       <div className="flex"> |  | ||||||
|                         {!selectedKnowledge && ( |  | ||||||
|                           <Tooltip title={t("tooltip.searchInternet")}> |  | ||||||
|                             <div className="inline-flex items-center gap-2"> |  | ||||||
|                               <PiGlobe |  | ||||||
|                                 className={`h-5 w-5 dark:text-gray-300 `} |  | ||||||
|                               /> |  | ||||||
|                               <Switch |  | ||||||
|                                 value={webSearch} |  | ||||||
|                                 onChange={(e) => setWebSearch(e)} |  | ||||||
|                                 checkedChildren={t("form.webSearch.on")} |  | ||||||
|                                 unCheckedChildren={t("form.webSearch.off")} |  | ||||||
|                               /> |  | ||||||
|                             </div> |  | ||||||
|                           </Tooltip> |  | ||||||
|                         )} |  | ||||||
|                       </div> |  | ||||||
|                       <div className="flex !justify-end gap-3"> |  | ||||||
|                         {!selectedKnowledge && ( |  | ||||||
|                           <Tooltip title={t("tooltip.uploadImage")}> |  | ||||||
|                             <button |  | ||||||
|                               type="button" |  | ||||||
|                               onClick={() => { |  | ||||||
|                                 inputRef.current?.click() |  | ||||||
|                               }} |  | ||||||
|                               className={`flex items-center justify-center dark:text-gray-300 ${ |  | ||||||
|                                 chatMode === "rag" ? "hidden" : "block" |  | ||||||
|                               }`}>
 |  | ||||||
|                               <ImageIcon className="h-5 w-5" /> |  | ||||||
|                             </button> |  | ||||||
|                           </Tooltip> |  | ||||||
|                         )} |  | ||||||
| 
 |  | ||||||
|                         {browserSupportsSpeechRecognition && ( |  | ||||||
|                           <Tooltip title={t("tooltip.speechToText")}> |  | ||||||
|                             <button |  | ||||||
|                               type="button" |  | ||||||
|                               onClick={async () => { |  | ||||||
|                                 if (isListening) { |  | ||||||
|                                   stopSpeechRecognition() |  | ||||||
|                                 } else { |  | ||||||
|                                   resetTranscript() |  | ||||||
|                                   startListening({ |  | ||||||
|                                     continuous: true, |  | ||||||
|                                     lang: speechToTextLanguage |  | ||||||
|                                   }) |  | ||||||
|                                 } |  | ||||||
|                               }} |  | ||||||
|                               className={`flex items-center justify-center dark:text-gray-300`}> |  | ||||||
|                               {!isListening ? ( |  | ||||||
|                                 <MicIcon className="h-5 w-5" /> |  | ||||||
|                               ) : ( |  | ||||||
|                                 <div className="relative"> |  | ||||||
|                                   <span className="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span> |  | ||||||
|                                   <MicIcon className="h-5 w-5" /> |  | ||||||
|                                 </div> |  | ||||||
|                               )} |  | ||||||
|                             </button> |  | ||||||
|                           </Tooltip> |  | ||||||
|                         )} |  | ||||||
|                         <KnowledgeSelect /> |  | ||||||
| 
 |  | ||||||
|                         {!isSending ? ( |  | ||||||
|                           <Dropdown.Button |  | ||||||
|                             htmlType="submit" |  | ||||||
|                             disabled={isSending} |  | ||||||
|                             className="!justify-end !w-auto" |  | ||||||
|                             icon={ |  | ||||||
|                               <svg |  | ||||||
|                                 xmlns="http://www.w3.org/2000/svg" |  | ||||||
|                                 fill="none" |  | ||||||
|                                 viewBox="0 0 24 24" |  | ||||||
|                                 strokeWidth={1.5} |  | ||||||
|                                 stroke="currentColor" |  | ||||||
|                                 className="w-5 h-5"> |  | ||||||
|                                 <path |  | ||||||
|                                   strokeLinecap="round" |  | ||||||
|                                   strokeLinejoin="round" |  | ||||||
|                                   d="m19.5 8.25-7.5 7.5-7.5-7.5" |  | ||||||
|                                 /> |  | ||||||
|                               </svg> |  | ||||||
|                             } |  | ||||||
|                             menu={{ |  | ||||||
|                               items: [ |  | ||||||
|                                 { |  | ||||||
|                                   key: 1, |  | ||||||
|                                   label: ( |  | ||||||
|                                     <Checkbox |  | ||||||
|                                       checked={sendWhenEnter} |  | ||||||
|                                       onChange={(e) => |  | ||||||
|                                         setSendWhenEnter(e.target.checked) |  | ||||||
|                                       }> |  | ||||||
|                                       {t("sendWhenEnter")} |  | ||||||
|                                     </Checkbox> |  | ||||||
|                                   ) |  | ||||||
|                                 }, |  | ||||||
|                                 { |  | ||||||
|                                   key: 2, |  | ||||||
|                                   label: ( |  | ||||||
|                                     <Checkbox |  | ||||||
|                                       checked={useOCR} |  | ||||||
|                                       onChange={(e) => |  | ||||||
|                                         setUseOCR(e.target.checked) |  | ||||||
|                                       }> |  | ||||||
|                                       {t("useOCR")} |  | ||||||
|                                     </Checkbox> |  | ||||||
|                                   ) |  | ||||||
|                                 } |  | ||||||
|                               ] |  | ||||||
|                             }}> |  | ||||||
|                             <div className="inline-flex gap-2"> |  | ||||||
|                               {sendWhenEnter ? ( |  | ||||||
|                                 <svg |  | ||||||
|                                   xmlns="http://www.w3.org/2000/svg" |  | ||||||
|                                   fill="none" |  | ||||||
|                                   stroke="currentColor" |  | ||||||
|                                   strokeLinecap="round" |  | ||||||
|                                   strokeLinejoin="round" |  | ||||||
|                                   strokeWidth="2" |  | ||||||
|                                   className="h-5 w-5" |  | ||||||
|                                   viewBox="0 0 24 24"> |  | ||||||
|                                   <path d="M9 10L4 15 9 20"></path> |  | ||||||
|                                   <path d="M20 4v7a4 4 0 01-4 4H4"></path> |  | ||||||
|                                 </svg> |  | ||||||
|                               ) : null} |  | ||||||
|                               {t("common:submit")} |  | ||||||
|                             </div> |  | ||||||
|                           </Dropdown.Button> |  | ||||||
|                         ) : ( |  | ||||||
|                           <Tooltip title={t("tooltip.stopStreaming")}> |  | ||||||
|                             <button |  | ||||||
|                               type="button" |  | ||||||
|                               onClick={stopStreamingRequest} |  | ||||||
|                               className="text-gray-800 dark:text-gray-300"> |  | ||||||
|                               <StopCircleIcon className="h-6 w-6" /> |  | ||||||
|                             </button> |  | ||||||
|                           </Tooltip> |  | ||||||
|                         )} |  | ||||||
|                       </div> |  | ||||||
|                     </div> |  | ||||||
|                   </div> |  | ||||||
|                 </form> |  | ||||||
|               </div> |  | ||||||
|               {form.errors.message && ( |  | ||||||
|                 <div className="text-red-500 text-center text-sm mt-1"> |  | ||||||
|                   {form.errors.message} |  | ||||||
|                 </div> |  | ||||||
|               )} |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   ) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ export default defineConfig({ | |||||||
|   outDir: "build", |   outDir: "build", | ||||||
| 
 | 
 | ||||||
|   manifest: { |   manifest: { | ||||||
|     version: "1.4.5", |     version: "1.4.6", | ||||||
|     name: |     name: | ||||||
|       process.env.TARGET === "firefox" |       process.env.TARGET === "firefox" | ||||||
|         ? "Page Assist - A Web UI for Local AI Models" |         ? "Page Assist - A Web UI for Local AI Models" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user