mirror of
				https://github.com/ggml-org/llama.cpp.git
				synced 2025-10-30 08:42:00 +00:00 
			
		
		
		
	server : (webui) revamp Settings dialog, add Pyodide interpreter (#11759)
* redo Settings modal UI * add python code interpreter * fix auto scroll * build * fix overflow for long output lines * bring back sticky copy button * adapt layout on mobile view * fix multiple lines output and color scheme * handle python exception * better state management * add webworker * add headers * format code * speed up by loading pyodide on page load * (small tweak) add small animation to make it feels like claude
This commit is contained in:
		| @@ -1,9 +1,11 @@ | ||||
| import { useEffect, useRef, useState } from 'react'; | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { useAppContext } from '../utils/app.context'; | ||||
| import StorageUtils from '../utils/storage'; | ||||
| import { useNavigate } from 'react-router'; | ||||
| import ChatMessage from './ChatMessage'; | ||||
| import { PendingMessage } from '../utils/types'; | ||||
| import { CanvasType, PendingMessage } from '../utils/types'; | ||||
| import { classNames } from '../utils/misc'; | ||||
| import CanvasPyInterpreter from './CanvasPyInterpreter'; | ||||
|  | ||||
| export default function ChatScreen() { | ||||
|   const { | ||||
| @@ -12,24 +14,24 @@ export default function ChatScreen() { | ||||
|     isGenerating, | ||||
|     stopGenerating, | ||||
|     pendingMessages, | ||||
|     canvasData, | ||||
|   } = useAppContext(); | ||||
|   const [inputMsg, setInputMsg] = useState(''); | ||||
|   const containerRef = useRef<HTMLDivElement>(null); | ||||
|   const navigate = useNavigate(); | ||||
|  | ||||
|   const currConvId = viewingConversation?.id ?? ''; | ||||
|   const pendingMsg: PendingMessage | undefined = pendingMessages[currConvId]; | ||||
|  | ||||
|   const scrollToBottom = (requiresNearBottom: boolean) => { | ||||
|     if (!containerRef.current) return; | ||||
|     const msgListElem = containerRef.current; | ||||
|     const mainScrollElem = document.getElementById('main-scroll'); | ||||
|     if (!mainScrollElem) return; | ||||
|     const spaceToBottom = | ||||
|       msgListElem.scrollHeight - | ||||
|       msgListElem.scrollTop - | ||||
|       msgListElem.clientHeight; | ||||
|       mainScrollElem.scrollHeight - | ||||
|       mainScrollElem.scrollTop - | ||||
|       mainScrollElem.clientHeight; | ||||
|     if (!requiresNearBottom || spaceToBottom < 50) { | ||||
|       setTimeout( | ||||
|         () => msgListElem.scrollTo({ top: msgListElem.scrollHeight }), | ||||
|         () => mainScrollElem.scrollTo({ top: mainScrollElem.scrollHeight }), | ||||
|         1 | ||||
|       ); | ||||
|     } | ||||
| @@ -58,66 +60,87 @@ export default function ChatScreen() { | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const hasCanvas = !!canvasData; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {/* chat messages */} | ||||
|     <div | ||||
|       className={classNames({ | ||||
|         'grid lg:gap-8 grow transition-[300ms]': true, | ||||
|         'grid-cols-[1fr_0fr] lg:grid-cols-[1fr_1fr]': hasCanvas, // adapted for mobile | ||||
|         'grid-cols-[1fr_0fr]': !hasCanvas, | ||||
|       })} | ||||
|     > | ||||
|       <div | ||||
|         id="messages-list" | ||||
|         className="flex flex-col grow overflow-y-auto" | ||||
|         ref={containerRef} | ||||
|         className={classNames({ | ||||
|           'flex flex-col w-full max-w-[900px] mx-auto': true, | ||||
|           'hidden lg:flex': hasCanvas, // adapted for mobile | ||||
|           flex: !hasCanvas, | ||||
|         })} | ||||
|       > | ||||
|         <div className="mt-auto flex justify-center"> | ||||
|           {/* placeholder to shift the message to the bottom */} | ||||
|           {viewingConversation ? '' : 'Send a message to start'} | ||||
|         {/* chat messages */} | ||||
|         <div id="messages-list" className="grow"> | ||||
|           <div className="mt-auto flex justify-center"> | ||||
|             {/* placeholder to shift the message to the bottom */} | ||||
|             {viewingConversation ? '' : 'Send a message to start'} | ||||
|           </div> | ||||
|           {viewingConversation?.messages.map((msg) => ( | ||||
|             <ChatMessage | ||||
|               key={msg.id} | ||||
|               msg={msg} | ||||
|               scrollToBottom={scrollToBottom} | ||||
|             /> | ||||
|           ))} | ||||
|  | ||||
|           {pendingMsg && ( | ||||
|             <ChatMessage | ||||
|               msg={pendingMsg} | ||||
|               scrollToBottom={scrollToBottom} | ||||
|               isPending | ||||
|               id="pending-msg" | ||||
|             /> | ||||
|           )} | ||||
|         </div> | ||||
|         {viewingConversation?.messages.map((msg) => ( | ||||
|           <ChatMessage key={msg.id} msg={msg} scrollToBottom={scrollToBottom} /> | ||||
|         ))} | ||||
|  | ||||
|         {pendingMsg && ( | ||||
|           <ChatMessage | ||||
|             msg={pendingMsg} | ||||
|             scrollToBottom={scrollToBottom} | ||||
|             isPending | ||||
|             id="pending-msg" | ||||
|           /> | ||||
|         {/* chat input */} | ||||
|         <div className="flex flex-row items-center pt-8 pb-6 sticky bottom-0 bg-base-100"> | ||||
|           <textarea | ||||
|             className="textarea textarea-bordered w-full" | ||||
|             placeholder="Type a message (Shift+Enter to add a new line)" | ||||
|             value={inputMsg} | ||||
|             onChange={(e) => setInputMsg(e.target.value)} | ||||
|             onKeyDown={(e) => { | ||||
|               if (e.key === 'Enter' && e.shiftKey) return; | ||||
|               if (e.key === 'Enter' && !e.shiftKey) { | ||||
|                 e.preventDefault(); | ||||
|                 sendNewMessage(); | ||||
|               } | ||||
|             }} | ||||
|             id="msg-input" | ||||
|             dir="auto" | ||||
|           ></textarea> | ||||
|           {isGenerating(currConvId) ? ( | ||||
|             <button | ||||
|               className="btn btn-neutral ml-2" | ||||
|               onClick={() => stopGenerating(currConvId)} | ||||
|             > | ||||
|               Stop | ||||
|             </button> | ||||
|           ) : ( | ||||
|             <button | ||||
|               className="btn btn-primary ml-2" | ||||
|               onClick={sendNewMessage} | ||||
|               disabled={inputMsg.trim().length === 0} | ||||
|             > | ||||
|               Send | ||||
|             </button> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className="w-full sticky top-[7em] h-[calc(100vh-9em)]"> | ||||
|         {canvasData?.type === CanvasType.PY_INTERPRETER && ( | ||||
|           <CanvasPyInterpreter /> | ||||
|         )} | ||||
|       </div> | ||||
|  | ||||
|       {/* chat input */} | ||||
|       <div className="flex flex-row items-center mt-8 mb-6"> | ||||
|         <textarea | ||||
|           className="textarea textarea-bordered w-full" | ||||
|           placeholder="Type a message (Shift+Enter to add a new line)" | ||||
|           value={inputMsg} | ||||
|           onChange={(e) => setInputMsg(e.target.value)} | ||||
|           onKeyDown={(e) => { | ||||
|             if (e.key === 'Enter' && e.shiftKey) return; | ||||
|             if (e.key === 'Enter' && !e.shiftKey) { | ||||
|               e.preventDefault(); | ||||
|               sendNewMessage(); | ||||
|             } | ||||
|           }} | ||||
|           id="msg-input" | ||||
|           dir="auto" | ||||
|         ></textarea> | ||||
|         {isGenerating(currConvId) ? ( | ||||
|           <button | ||||
|             className="btn btn-neutral ml-2" | ||||
|             onClick={() => stopGenerating(currConvId)} | ||||
|           > | ||||
|             Stop | ||||
|           </button> | ||||
|         ) : ( | ||||
|           <button | ||||
|             className="btn btn-primary ml-2" | ||||
|             onClick={sendNewMessage} | ||||
|             disabled={inputMsg.trim().length === 0} | ||||
|           > | ||||
|             Send | ||||
|           </button> | ||||
|         )} | ||||
|       </div> | ||||
|     </> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Xuan-Son Nguyen
					Xuan-Son Nguyen