1212 .markdown {
1313 h1 , h2 , h3 , h4 , h5 , h6 , ul , ol , li { all : revert; }
1414 pre {
15- @apply whitespace-pre-wrap my-4 rounded-lg p-2;
15+ @apply whitespace-pre-wrap rounded-lg p-2;
1616 border : 1px solid currentColor;
1717 }
1818 /* TODO: fix markdown table */
2525 .bg-base-200 {background-color : var (--fallback-b2 , oklch (var (--b2 )/ 1 ))}
2626 .bg-base-300 {background-color : var (--fallback-b3 , oklch (var (--b3 )/ 1 ))}
2727 .text-base-content {color : var (--fallback-bc , oklch (var (--bc )/ 1 ))}
28+ .show-on-hover {
29+ @apply opacity-0 group-hover:opacity-100;
30+ }
2831 .btn-mini {
29- @apply cursor-pointer opacity-0 group-hover:opacity-100 hover:shadow-md;
32+ @apply cursor-pointer hover:shadow-md;
3033 }
3134 .chat-screen { max-width : 900px ; }
3235 /* because the default bubble color is quite dark, we will make a custom one using bg-base-300 */
@@ -152,14 +155,14 @@ <h2 class="font-bold mb-4 ml-4">Conversations</h2>
152155 <!-- actions for each message -->
153156 < div :class ="{'text-right': msg.role === 'user'} " class ="mx-4 mt-2 mb-2 ">
154157 <!-- user message -->
155- < button v-if ="msg.role === 'user' " class ="badge btn-mini " @click ="editingMsg = msg " :disabled ="isGenerating ">
158+ < button v-if ="msg.role === 'user' " class ="badge btn-minishow-on-hover " @click ="editingMsg = msg " :disabled ="isGenerating ">
156159 ✍️ Edit
157160 </ button >
158161 <!-- assistant message -->
159- < button v-if ="msg.role === 'assistant' " class ="badge btn-mini mr-2 " @click ="regenerateMsg(msg) " :disabled ="isGenerating ">
162+ < button v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="regenerateMsg(msg) " :disabled ="isGenerating ">
160163 🔄 Regenerate
161164 </ button >
162- < button v-if ="msg.role === 'assistant' " class ="badge btn-mini mr-2 " @click ="copyMsg(msg) " :disabled ="isGenerating ">
165+ < button v-if ="msg.role === 'assistant' " class ="badge btn-mini show-on-hover mr-2 " @click ="copyMsg(msg) " :disabled ="isGenerating ">
163166 📋 Copy
164167 </ button >
165168 </ div >
@@ -196,20 +199,21 @@ <h2 class="font-bold mb-4 ml-4">Conversations</h2>
196199 < h3 class ="text-lg font-bold mb-6 "> Settings</ h3 >
197200 < div class ="h-[calc(90vh-12rem)] overflow-y-auto ">
198201 < p class ="opacity-40 mb-6 "> Settings below are saved in browser's localStorage</ p >
202+ < settings-modal-short-input :config-key ="'apiKey' " :config-default ="configDefault " :config-info ="configInfo " v-model ="config.apiKey "> </ settings-modal-short-input >
199203 < label class ="form-control mb-2 ">
200204 < div class ="label "> System Message</ div >
201205 < textarea class ="textarea textarea-bordered h-24 " :placeholder ="'Default: ' + configDefault.systemMessage " v-model ="config.systemMessage "> </ textarea >
202206 </ label >
203207 < template v-for ="configKey in ['temperature', 'top_k', 'top_p', 'min_p', 'max_tokens'] ">
204- < settings-modal-numeric -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
208+ < settings-modal-short -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
205209 </ template >
206210 <!-- TODO: add more sampling-related configs, please regroup them into different "collapse" sections -->
207211 <!-- Section: Other sampler settings -->
208212 < details class ="collapse collapse-arrow bg-base-200 mb-2 overflow-visible ">
209213 < summary class ="collapse-title font-bold "> Other sampler settings</ summary >
210214 < div class ="collapse-content ">
211215 < template v-for ="configKey in ['dynatemp_range', 'dynatemp_exponent', 'typical_p', 'xtc_probability', 'xtc_threshold'] ">
212- < settings-modal-numeric -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
216+ < settings-modal-short -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
213217 </ template >
214218 </ div >
215219 </ details >
@@ -218,7 +222,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
218222 < summary class ="collapse-title font-bold "> Penalties settings</ summary >
219223 < div class ="collapse-content ">
220224 < template v-for ="configKey in ['repeat_last_n', 'repeat_penalty', 'presence_penalty', 'frequency_penalty', 'dry_multiplier', 'dry_base', 'dry_allowed_length', 'dry_penalty_last_n'] ">
221- < settings-modal-numeric -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
225+ < settings-modal-short -input :config-key ="configKey " :config-default ="configDefault " :config-info ="configInfo " v-model ="config[configKey] " />
222226 </ template >
223227 </ div >
224228 </ details >
@@ -245,7 +249,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
245249 </ div >
246250
247251 <!-- Template to be used by settings modal -->
248- < template id ="settings-modal-numeric -input ">
252+ < template id ="settings-modal-short -input ">
249253 < label class ="input input-bordered join-item grow flex items-center gap-2 mb-2 ">
250254 <!-- Show help message on hovering on the input label -->
251255 < div class ="dropdown dropdown-hover ">
@@ -264,9 +268,13 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
264268 import { createApp , defineComponent , shallowRef , computed , h } from './deps_vue.esm-browser.js' ;
265269 import { llama } from './completion.js' ;
266270
271+ // utility functions
267272 const isString = ( x ) => ! ! x . toLowerCase ;
268273 const isNumeric = ( n ) => ! isString ( n ) && ! isNaN ( n ) ;
274+ const escapeAttr = ( str ) => str . replace ( / > / g, '>' ) . replace ( / " / g, '"' ) ;
275+ const copyStr = ( str ) => navigator . clipboard . writeText ( str ) ;
269276
277+ // constants
270278 const BASE_URL = localStorage . getItem ( 'base' ) // for debugging
271279 || ( new URL ( '.' , document . baseURI ) . href ) . toString ( ) ; // for production
272280 const CONFIG_DEFAULT = {
@@ -295,7 +303,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
295303 custom : '' , // custom json-stringified object
296304 } ;
297305 const CONFIG_INFO = {
298- apiKey : '' ,
306+ apiKey : 'Set the API Key if you are using --api-key option for the server. ' ,
299307 systemMessage : 'The starting message that defines how model should behave.' ,
300308 temperature : 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.' ,
301309 dynatemp_range : 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.' ,
@@ -325,19 +333,28 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
325333 // markdown support
326334 const VueMarkdown = defineComponent (
327335 ( props ) => {
328- const md = shallowRef ( new markdownit ( props . options ?? { breaks : true } ) ) ;
329- for ( const plugin of props . plugins ?? [ ] ) {
330- md . value . use ( plugin ) ;
331- }
336+ const md = shallowRef ( new markdownit ( { breaks : true } ) ) ;
337+ const origFenchRenderer = md . value . renderer . rules . fence ;
338+ md . value . renderer . rules . fence = ( tokens , idx , ...args ) => {
339+ const content = tokens [ idx ] . content ;
340+ const origRendered = origFenchRenderer ( tokens , idx , ...args ) ;
341+ return `<div class="relative my-4">
342+ <div class="text-right sticky top-4 mb-2 mr-2 h-0">
343+ <button class="badge btn-mini" onclick="copyStr(${ escapeAttr ( JSON . stringify ( content ) ) } )">📋 Copy</button>
344+ </div>
345+ ${ origRendered }
346+ </div>` ;
347+ } ;
348+ window . copyStr = copyStr ;
332349 const content = computed ( ( ) => md . value . render ( props . source ) ) ;
333350 return ( ) => h ( "div" , { innerHTML : content . value } ) ;
334351 } ,
335- { props : [ "source" , "options" , "plugins" ] }
352+ { props : [ "source" ] }
336353 ) ;
337354
338355 // inout field to be used by settings modal
339- const SettingsModalNumericInput = defineComponent ( {
340- template : document . getElementById ( 'settings-modal-numeric -input' ) . innerHTML ,
356+ const SettingsModalShortInput = defineComponent ( {
357+ template : document . getElementById ( 'settings-modal-short -input' ) . innerHTML ,
341358 props : [ 'configKey' , 'configDefault' , 'configInfo' , 'modelValue' ] ,
342359 } ) ;
343360
@@ -390,7 +407,11 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
390407 if ( ! conv ) return ;
391408 const msg = conv . messages . pop ( ) ;
392409 conv . lastModified = Date . now ( ) ;
393- localStorage . setItem ( convId , JSON . stringify ( conv ) ) ;
410+ if ( conv . messages . length === 0 ) {
411+ StorageUtils . remove ( convId ) ;
412+ } else {
413+ localStorage . setItem ( convId , JSON . stringify ( conv ) ) ;
414+ }
394415 return msg ;
395416 } ,
396417
@@ -431,7 +452,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
431452 const mainApp = createApp ( {
432453 components : {
433454 VueMarkdown,
434- SettingsModalNumericInput ,
455+ SettingsModalShortInput ,
435456 } ,
436457 data ( ) {
437458 return {
@@ -587,6 +608,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
587608 this . isGenerating = false ;
588609 this . stopGeneration = ( ) => { } ;
589610 this . fetchMessages ( ) ;
611+ chatScrollToBottom ( ) ;
590612 } ,
591613
592614 // message actions
@@ -600,7 +622,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
600622 this . generateMessage ( currConvId ) ;
601623 } ,
602624 copyMsg ( msg ) {
603- navigator . clipboard . writeText ( msg . content ) ;
625+ copyStr ( msg . content ) ;
604626 } ,
605627 editUserMsgAndRegenerate ( msg ) {
606628 if ( this . isGenerating ) return ;
0 commit comments