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 >
@@ -254,7 +258,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
254258 </ div >
255259
256260 <!-- Template to be used by settings modal -->
257- < template id ="settings-modal-numeric -input ">
261+ < template id ="settings-modal-short -input ">
258262 < label class ="input input-bordered join-item grow flex items-center gap-2 mb-2 ">
259263 <!-- Show help message on hovering on the input label -->
260264 < div class ="dropdown dropdown-hover ">
@@ -273,9 +277,13 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
273277 import { createApp , defineComponent , shallowRef , computed , h } from './deps_vue.esm-browser.js' ;
274278 import { llama } from './completion.js' ;
275279
280+ // utility functions
276281 const isString = ( x ) => ! ! x . toLowerCase ;
277282 const isNumeric = ( n ) => ! isString ( n ) && ! isNaN ( n ) ;
283+ const escapeAttr = ( str ) => str . replace ( / > / g, '>' ) . replace ( / " / g, '"' ) ;
284+ const copyStr = ( str ) => navigator . clipboard . writeText ( str ) ;
278285
286+ // constants
279287 const BASE_URL = localStorage . getItem ( 'base' ) // for debugging
280288 || ( new URL ( '.' , document . baseURI ) . href ) . toString ( ) ; // for production
281289 const CONFIG_DEFAULT = {
@@ -305,7 +313,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
305313 custom : '' , // custom json-stringified object
306314 } ;
307315 const CONFIG_INFO = {
308- apiKey : '' ,
316+ apiKey : 'Set the API Key if you are using --api-key option for the server. ' ,
309317 systemMessage : 'The starting message that defines how model should behave.' ,
310318 samplers : 'The order at which samplers are applied, in simplified way. Default is "dkypmxt": dry->top_k->typ_p->top_p->min_p->xtc->temperature' ,
311319 temperature : 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.' ,
@@ -336,19 +344,28 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
336344 // markdown support
337345 const VueMarkdown = defineComponent (
338346 ( props ) => {
339- const md = shallowRef ( new markdownit ( props . options ?? { breaks : true } ) ) ;
340- for ( const plugin of props . plugins ?? [ ] ) {
341- md . value . use ( plugin ) ;
342- }
347+ const md = shallowRef ( new markdownit ( { breaks : true } ) ) ;
348+ const origFenchRenderer = md . value . renderer . rules . fence ;
349+ md . value . renderer . rules . fence = ( tokens , idx , ...args ) => {
350+ const content = tokens [ idx ] . content ;
351+ const origRendered = origFenchRenderer ( tokens , idx , ...args ) ;
352+ return `<div class="relative my-4">
353+ <div class="text-right sticky top-4 mb-2 mr-2 h-0">
354+ <button class="badge btn-mini" onclick="copyStr(${ escapeAttr ( JSON . stringify ( content ) ) } )">📋 Copy</button>
355+ </div>
356+ ${ origRendered }
357+ </div>` ;
358+ } ;
359+ window . copyStr = copyStr ;
343360 const content = computed ( ( ) => md . value . render ( props . source ) ) ;
344361 return ( ) => h ( "div" , { innerHTML : content . value } ) ;
345362 } ,
346- { props : [ "source" , "options" , "plugins" ] }
363+ { props : [ "source" ] }
347364 ) ;
348365
349366 // inout field to be used by settings modal
350- const SettingsModalNumericInput = defineComponent ( {
351- template : document . getElementById ( 'settings-modal-numeric -input' ) . innerHTML ,
367+ const SettingsModalShortInput = defineComponent ( {
368+ template : document . getElementById ( 'settings-modal-short -input' ) . innerHTML ,
352369 props : [ 'configKey' , 'configDefault' , 'configInfo' , 'modelValue' ] ,
353370 } ) ;
354371
@@ -401,7 +418,11 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
401418 if ( ! conv ) return ;
402419 const msg = conv . messages . pop ( ) ;
403420 conv . lastModified = Date . now ( ) ;
404- localStorage . setItem ( convId , JSON . stringify ( conv ) ) ;
421+ if ( conv . messages . length === 0 ) {
422+ StorageUtils . remove ( convId ) ;
423+ } else {
424+ localStorage . setItem ( convId , JSON . stringify ( conv ) ) ;
425+ }
405426 return msg ;
406427 } ,
407428
@@ -442,7 +463,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
442463 const mainApp = createApp ( {
443464 components : {
444465 VueMarkdown,
445- SettingsModalNumericInput ,
466+ SettingsModalShortInput ,
446467 } ,
447468 data ( ) {
448469 return {
@@ -599,6 +620,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
599620 this . isGenerating = false ;
600621 this . stopGeneration = ( ) => { } ;
601622 this . fetchMessages ( ) ;
623+ chatScrollToBottom ( ) ;
602624 } ,
603625
604626 // message actions
@@ -612,7 +634,7 @@ <h3 class="text-lg font-bold mb-6">Settings</h3>
612634 this . generateMessage ( currConvId ) ;
613635 } ,
614636 copyMsg ( msg ) {
615- navigator . clipboard . writeText ( msg . content ) ;
637+ copyStr ( msg . content ) ;
616638 } ,
617639 editUserMsgAndRegenerate ( msg ) {
618640 if ( this . isGenerating ) return ;
0 commit comments