1515      <!-- sidebar --> 
1616      < div  class ="drawer-side h-screen lg:h-screen z-50 lg:max-w-64 "> 
1717        < label  for ="toggle-drawer " aria-label ="close sidebar " class ="drawer-overlay "> </ label > 
18-         < div  class ="flex flex-col bg-base-200 min-h-full max-w-[calc(100vw-2em)]  py-4 px-4 "> 
18+         < div  class ="flex flex-col bg-base-200 min-h-full max-w-64  py-4 px-4 "> 
1919          < div  class ="flex flex-row items-center justify-between mb-4 mt-4 "> 
2020            < h2  class ="font-bold ml-4 "> Conversations</ h2 > 
2121
@@ -120,51 +120,25 @@ <h2 class="font-bold ml-4">Conversations</h2>
120120            {{ messages.length === 0 ? 'Send a message to start' : '' }}
121121          </ div > 
122122          < div  v-for ="msg in messages " class ="group "> 
123-             < div  :class ="{ 
124-               'chat': true, 
125-               'chat-start': msg.role !== 'user', 
126-               'chat-end': msg.role === 'user', 
127-             } "> 
128-               < div  :class ="{ 
129-                 'chat-bubble markdown': true, 
130-                 'chat-bubble-base-300': msg.role !== 'user', 
131-               } "> 
132-                 <!-- textarea for editing message --> 
133-                 < template  v-if ="editingMsg && editingMsg.id === msg.id "> 
134-                   < textarea 
135-                     class ="textarea textarea-bordered bg-base-100 text-base-content w-[calc(90vw-8em)] lg:w-96 "
136-                     v-model ="msg.content "> </ textarea > 
137-                   < br /> 
138-                   < button  class ="btn btn-ghost mt-2 mr-2 " @click ="editingMsg = null "> Cancel</ button > 
139-                   < button  class ="btn mt-2 " @click ="editUserMsgAndRegenerate(msg) "> Submit</ button > 
140-                 </ template > 
141-                 <!-- render message as markdown --> 
142-                 < vue-markdown  v-else  :source ="msg.content " /> 
143-               </ div > 
144-             </ div > 
145- 
146-             <!-- actions for each message --> 
147-             < div  :class ="{'text-right': msg.role === 'user'} " class ="mx-4 mt-2 mb-2 "> 
148-               <!-- user message --> 
149-               < button  v-if ="msg.role === 'user' " class ="badge btn-mini show-on-hover " @click ="editingMsg = msg " :disabled ="isGenerating "> 
150-                 ✍️ Edit
151-               </ button > 
152-               <!-- assistant message --> 
153-               < button  v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="regenerateMsg(msg) " :disabled ="isGenerating "> 
154-                 🔄 Regenerate
155-               </ button > 
156-               < button  v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="copyMsg(msg) " :disabled ="isGenerating "> 
157-                 📋 Copy
158-               </ button > 
159-             </ div > 
123+             < message-bubble 
124+               :config ="config "
125+               :msg ="msg "
126+               :key ="msg.id "
127+               :is-generating ="isGenerating "
128+               :edit-user-msg-and-regenerate ="editUserMsgAndRegenerate "
129+               :regenerate-msg ="regenerateMsg "> </ message-bubble > 
160130          </ div > 
161131
162132          <!-- pending (ongoing) assistant message --> 
163-           < div  id ="pending-msg " class ="chat chat-start "> 
164-             < div  v-if ="pendingMsg " class ="chat-bubble markdown chat-bubble-base-300 "> 
165-               < span  v-if ="!pendingMsg.content " class ="loading loading-dots loading-md "> </ span > 
166-               < vue-markdown  v-else  :source ="pendingMsg.content " /> 
167-             </ div > 
133+           < div  id ="pending-msg " class ="group "> 
134+             < message-bubble 
135+               v-if ="pendingMsg "
136+               :config ="config "
137+               :msg ="pendingMsg "
138+               :key ="pendingMsg.id "
139+               :is-generating ="isGenerating "
140+               :edit-user-msg-and-regenerate ="() => {} "
141+               :regenerate-msg ="() => {} "> </ message-bubble > 
168142          </ div > 
169143        </ div > 
170144
@@ -227,6 +201,10 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
227201          < details  class ="collapse collapse-arrow bg-base-200 mb-2 overflow-visible "> 
228202            < summary  class ="collapse-title font-bold "> Advanced config</ summary > 
229203            < div  class ="collapse-content "> 
204+               < div  class ="flex flex-row items-center mb-2 "> 
205+                 < input  type ="checkbox " class ="checkbox " v-model ="config.showTokensPerSecond " /> 
206+                 < span  class ="ml-4 "> Show tokens per second</ span > 
207+               </ div > 
230208              < label  class ="form-control mb-2 "> 
231209                <!-- Custom parameters input --> 
232210                < div  class ="label inline "> Custom JSON config (For more info, refer to < a  class ="underline " href ="https://github.com/ggerganov/llama.cpp/blob/master/examples/server/README.md " target ="_blank " rel ="noopener noreferrer "> server documentation</ a > )</ div > 
@@ -247,6 +225,66 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
247225
248226  </ div > 
249227
228+ 
229+   <!-- Template to be used as message bubble --> 
230+   < template  id ="message-bubble "> 
231+     < div  :class ="{ 
232+       'chat': true, 
233+       'chat-start': msg.role !== 'user', 
234+       'chat-end': msg.role === 'user', 
235+     } "> 
236+       < div  :class ="{ 
237+         'chat-bubble markdown': true, 
238+         'chat-bubble-base-300': msg.role !== 'user', 
239+       } "> 
240+         <!-- textarea for editing message --> 
241+         < template  v-if ="editingContent !== null "> 
242+           < textarea 
243+             class ="textarea textarea-bordered bg-base-100 text-base-content w-[calc(90vw-8em)] lg:w-96 "
244+             v-model ="editingContent "> </ textarea > 
245+           < br /> 
246+           < button  class ="btn btn-ghost mt-2 mr-2 " @click ="editingContent = null "> Cancel</ button > 
247+           < button  class ="btn mt-2 " @click ="editMsg() "> Submit</ button > 
248+         </ template > 
249+         < template  v-else > 
250+           <!-- show loading dots for pending message --> 
251+           < span  v-if ="msg.content === null " class ="loading loading-dots loading-md "> </ span > 
252+           <!-- render message as markdown --> 
253+           < vue-markdown  v-else  :source ="msg.content "> </ vue-markdown > 
254+           <!-- render timings if enabled --> 
255+           < div  class ="dropdown dropdown-hover dropdown-top mt-2 " v-if ="timings && config.showTokensPerSecond "> 
256+             < div  tabindex ="0 " role ="button " class ="cursor-pointer font-semibold text-sm opacity-60 "> Speed: {{ timings.predicted_per_second.toFixed(1) }} t/s</ div > 
257+             < div  class ="dropdown-content bg-base-100 z-10 w-64 p-2 shadow mt-4 "> 
258+               < b > Prompt</ b > < br /> 
259+               - Tokens: {{ timings.prompt_n }}< br /> 
260+               - Time: {{ timings.prompt_ms }} ms< br /> 
261+               - Speed: {{ timings.prompt_per_second.toFixed(1) }} t/s< br /> 
262+               < b > Generation</ b > < br /> 
263+               - Tokens: {{ timings.predicted_n }}< br /> 
264+               - Time: {{ timings.predicted_ms }} ms< br /> 
265+               - Speed: {{ timings.predicted_per_second.toFixed(1) }} t/s< br /> 
266+             </ div > 
267+           </ div > 
268+         </ template > 
269+       </ div > 
270+     </ div > 
271+     <!-- actions for each message --> 
272+     < div  :class ="{'text-right': msg.role === 'user', 'opacity-0': isGenerating} " class ="mx-4 mt-2 mb-2 "> 
273+       <!-- user message --> 
274+       < button  v-if ="msg.role === 'user' " class ="badge btn-mini show-on-hover " @click ="editingContent = msg.content " :disabled ="isGenerating "> 
275+         ✍️ Edit
276+       </ button > 
277+       <!-- assistant message --> 
278+       < button  v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="regenerateMsg(msg) " :disabled ="isGenerating "> 
279+         🔄 Regenerate
280+       </ button > 
281+       < button  v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="copyMsg() " :disabled ="isGenerating "> 
282+         📋 Copy
283+       </ button > 
284+     </ div > 
285+   </ template > 
286+ 
287+ 
250288  <!-- Template to be used by settings modal --> 
251289  < template  id ="settings-modal-short-input "> 
252290    < label  class ="input input-bordered join-item grow flex items-center gap-2 mb-2 "> 
0 commit comments