179179 display : flex;
180180 }
181181
182+ .conversation-area {
183+ margin : 6px 4px 4px 4px ;
184+ /* top left bot right*/
185+ }
186+
182187 .fade-enter-active ,
183188 .fade-leave-active {
184189 transition : all 0.5s ease;
217222 }
218223 }
219224
225+ .md-preview {
226+ width : 100vw ;
227+ max-width : 100% ;
228+ }
229+
220230 .md-editor-preview p {
221231 word-break : break-word;
222232 }
369379 @click ="messageStore.sendMessage " icon ="mdi-arrow-up ">
370380 </ v-btn >
371381 < v-btn v-else-if ="messageStore.generating " size ="small " color ="primary " variant ="elevated "
372- @click ="snackbarStore.showInfoMessage('$vuetify.dataIterator.snackbar.stopped') "
382+ @click ="messageStore.stop "
373383 icon ="mdi-stop "> </ v-btn >
374384 < v-btn v-else-if ="messageStore.conversation.length > 0 " size ="small " color ="primary "
375385 variant ="elevated " @click ="messageStore.resendMessage " icon ="mdi-autorenew "> </ v-btn >
@@ -722,20 +732,27 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
722732 < div v-for ="(message, index) in messages ">
723733 < div v-if ="message.role === 'user' ">
724734 < div class ="px-2 py-5 user-message ">
725- < div class ="message text-pre-wrap ">
735+ < div class ="message ">
726736 < v-avatar :size =size class ="mt-3 mr-3 mr-lg-6 " color ="primary " icon ="mdi-account-circle ">
727737 </ v-avatar >
728- < tuui-chat-card class ="gradient text-pre-wrap " :index ="index " :messages ="messages ">
729- < v-card-text v-if ="Array.isArray(message.content) ">
730- < div v-for ="(item, index) in message.content " :key ="index ">
731- < div v-if ="item.type=='text' "> {{ item.text }}</ div >
732- < tuui-img-dialog v-if ="item.type=='image_url' "
733- :src ="item.image_url.url "> </ tuui-img-dialog >
734- </ div >
735- </ v-card-text >
736- < v-card-text v-else >
737- {{ message.content }}
738- </ v-card-text >
738+ < tuui-chat-card class ="gradient text-pre-wrap " :index ="index " :messages ="messages "
739+ :show-modify ="true ">
740+ < template v-slot:default ="{ showmodify } ">
741+ < v-card-text v-if ="Array.isArray(message.content) " class ="md-preview ">
742+ < div v-for ="(item, index) in message.content " :key ="index ">
743+ < tuui-img-dialog v-if ="item.type=='image_url' "
744+ :src ="item.image_url.url "> </ tuui-img-dialog >
745+ < v-textarea class ="conversation-area " variant ="plain " density ='compact '
746+ auto-grow :counter ="showmodify " :hide-details ="!showmodify " rows ="1 "
747+ :readonly ="!showmodify " v-model ="item.text "> </ v-textarea >
748+ </ div >
749+ </ v-card-text >
750+ < v-card-text v-else class ="md-preview pt-1 ">
751+ < v-textarea class ="conversation-area " variant ="plain " density ='compact ' auto-grow
752+ rows ="1 " :readonly ="!showmodify " :counter ="showmodify "
753+ :hide-details ="!showmodify " v-model ="message.content "> </ v-textarea >
754+ </ v-card-text >
755+ </ template >
739756 </ tuui-chat-card >
740757 </ div >
741758 </ div >
@@ -748,12 +765,14 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
748765 < tuui-chat-card :index ="index " :messages ="messages " :show-content ="true ">
749766 < template v-slot:default ="{ showcontent } ">
750767 < div v-if ="showcontent ">
751- < v-card-text style ="white-space: pre-wrap; ">
752- {{ message.content }}
768+ < v-card-text class ="md-preview pt-1 ">
769+ < v-textarea class ="conversation-area " variant ="plain " density ='compact '
770+ auto-grow hide-details rows ="1 " readonly
771+ v-model ="message.content "> </ v-textarea >
753772 </ v-card-text >
754773 </ div >
755774 < div v-else >
756- < md-preview :model-value ="message.content "
775+ < md-preview :model-value ="message.content " class =" md-preview "
757776 :language ="language == 'zhHans' ? 'zh-CN' : 'en-US' " :code-foldable ="true "
758777 auto-fold-threshold ="Infinity "> </ md-preview >
759778 </ div >
@@ -782,16 +801,22 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
782801 < v-hover open-delay ="100 ">
783802 < template v-slot:default ="{ isHovering, props } ">
784803 < v-card v-bind ="props " :elevation ="isHovering ? 4 : 2 ">
785- < slot :showcontent ="showcontent "> </ slot >
804+ < slot :showcontent ="showcontent " :showmodify =" showmodify " > </ slot >
786805 < v-expand-transition >
787806 < div v-if ="isHovering ">
788807 < v-divider > </ v-divider >
789808 < v-card-actions >
790809 < v-spacer > </ v-spacer >
810+ < v-btn color ="primary " icon ="mdi-content-copy " size ="x-small " variant ="plain "
811+ @click ="copyToClipboard(messages[index]) "> </ v-btn >
812+ < v-btn v-if ="showModify " color ="primary "
813+ :icon =" showmodify ? 'mdi-check-bold' : 'mdi-lead-pencil' " size ="x-small "
814+ variant ="plain " @click ="showmodify = !showmodify " v-bind ="showmodify "> </ v-btn >
815+ < v-btn v-if ="showContent " color ="primary "
816+ :icon =" showcontent ? 'mdi-eye-remove' : 'mdi-eye' " size ="x-small " variant ="plain "
817+ @click ="showcontent = !showcontent " v-bind ="showcontent "> </ v-btn >
791818 < v-btn v-if ="showDelete " color ="error " icon ="mdi-delete-off-outline " size ="x-small "
792819 variant ="plain " @click ="messages.splice(index, 1) "> </ v-btn >
793- < v-btn v-if ="showContent " color ="primary " icon ="mdi-eye " size ="x-small " variant ="plain "
794- @click ="showcontent = !showcontent " v-bind ="showcontent "> </ v-btn >
795820 </ v-card-actions >
796821 </ div >
797822 </ v-expand-transition >
@@ -807,6 +832,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
807832 const { createI18n, useI18n } = VueI18n
808833 const { en, it, ja, sv, zhHans } = 'vuetify/locale'
809834
835+ const useSnackbarStore = defineStore ( {
836+ id : "snackbarStore" ,
837+ state : ( ) => ( {
838+ isShow : false ,
839+ message : "" ,
840+ type : "" ,
841+ } ) ,
842+
843+ actions : {
844+ showMessage ( message , type = "" ) {
845+ this . isShow = true ;
846+ this . message = message ;
847+ this . type = type ;
848+ } ,
849+
850+ showErrorMessage ( message ) {
851+ this . showMessage ( message , "error" ) ;
852+ } ,
853+ showSuccessMessage ( message ) {
854+ this . showMessage ( message , "success" ) ;
855+ } ,
856+ showInfoMessage ( message ) {
857+ this . showMessage ( message , "info" ) ;
858+ } ,
859+ showWarningMessage ( message ) {
860+ this . showMessage ( message , "warning" ) ;
861+ } ,
862+ getIcon ( ) {
863+ const icon = {
864+ info : "mdi-information" ,
865+ success : "mdi-check-circle" ,
866+ error : "mdi-alert-circle" ,
867+ warning : "mdi-alert" ,
868+ } ;
869+
870+ return icon [ this . type ] ;
871+ } ,
872+ } ,
873+ } ) ;
874+
810875 const TuuiImgDialog = {
811876 template : '#tuui-img-dialog-template' ,
812877 props : { src : { type : String , required : true } }
@@ -818,22 +883,46 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
818883 props : {
819884 index : { type : Number , required : true } ,
820885 messages : { type : Object , required : true } ,
821- showDelete : { type : Boolean , default : true } ,
822886 showContent : { type : Boolean , default : false } ,
887+ showDelete : { type : Boolean , default : true } ,
888+ showModify : { type : Boolean , default : false } ,
823889 } ,
824890 setup ( props ) {
825891 const showcontent = ref ( false ) ;
892+ const showmodify = ref ( false ) ;
893+ const snackbarStore = useSnackbarStore ( ) ;
894+
895+ const copyToClipboard = async ( msg ) => {
896+ let textToCopy = '' ;
897+ try {
898+ if ( typeof msg . content === 'string' ) {
899+ textToCopy = msg . content ;
900+ } else if ( Array . isArray ( msg . content ) ) {
901+ for ( const item of msg . content ) {
902+ if ( item . type === 'text' && typeof item . text === 'string' ) {
903+ textToCopy = item . text ;
904+ }
905+ }
906+ }
907+ await navigator . clipboard . writeText ( textToCopy ) ;
908+ snackbarStore . showSuccessMessage ( '$vuetify.dataIterator.snackbar.copied' )
909+ } catch ( err ) {
910+ snackbarStore . showErrorMessage ( err )
911+ }
912+ } ;
826913
827914 return {
915+ copyToClipboard,
828916 showcontent,
917+ showmodify,
829918 } ;
830919 }
831920 } ) ;
832921 const TuuiChatBox = {
833922 template : '#tuui-chat-box-template' ,
834923 components : {
835924 TuuiImgDialog,
836- TuuiChatCard
925+ TuuiChatCard,
837926 } ,
838927 props : {
839928 messages : { type : Object , required : true } ,
@@ -846,9 +935,12 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
846935 components : {
847936 vuedraggable,
848937 TuuiChatBox,
938+ TuuiChatCard
849939 } ,
850940 setup ( ) {
851941
942+ const snackbarStore = useSnackbarStore ( ) ;
943+
852944 const useAgentStore = defineStore ( {
853945 id : "agentStore" ,
854946 state : ( ) => ( {
@@ -1065,54 +1157,6 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
10651157 } ,
10661158 } ) ;
10671159
1068- const useSnackbarStore = defineStore ( {
1069- id : "snackbarStore" ,
1070- state : ( ) => ( {
1071- isShow : false ,
1072- message : "" ,
1073- type : "" ,
1074- } ) ,
1075-
1076- persist : {
1077- enabled : true ,
1078- strategies : [ { storage : localStorage , paths : [ "" ] } ] ,
1079- } ,
1080-
1081- getters : { } ,
1082- actions : {
1083- showMessage ( message , type = "" ) {
1084- this . isShow = true ;
1085- this . message = message ;
1086- this . type = type ;
1087- messageStore . generating = false
1088- } ,
1089-
1090- showErrorMessage ( message ) {
1091- this . showMessage ( message , "error" ) ;
1092- } ,
1093- showSuccessMessage ( message ) {
1094- this . showMessage ( message , "success" ) ;
1095- } ,
1096- showInfoMessage ( message ) {
1097- this . showMessage ( message , "info" ) ;
1098- } ,
1099- showWarningMessage ( message ) {
1100- this . showMessage ( message , "warning" ) ;
1101- } ,
1102- getIcon ( ) {
1103- const icon = {
1104- info : "mdi-information" ,
1105- success : "mdi-check-circle" ,
1106- error : "mdi-alert-circle" ,
1107- warning : "mdi-alert" ,
1108- } ;
1109-
1110- return icon [ this . type ] ;
1111- } ,
1112- } ,
1113- } ) ;
1114-
1115-
11161160 const useMessageStore = defineStore ( {
11171161 id : "messageStore" ,
11181162 state : ( ) => ( {
@@ -1131,6 +1175,10 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
11311175 snackbarStore . showSuccessMessage ( '$vuetify.dataIterator.snackbar.addnew' )
11321176 }
11331177 } ,
1178+ stop ( ) {
1179+ this . generating = false ,
1180+ snackbarStore . showInfoMessage ( '$vuetify.dataIterator.snackbar.stopped' )
1181+ } ,
11341182 clear ( ) {
11351183 this . userMessage = "" ;
11361184 this . images = [ ] ;
@@ -1184,16 +1232,15 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
11841232 role : "user" ,
11851233 } ) ;
11861234
1235+ if ( this . conversation . length == 1 ) {
1236+ historyStore . init ( this . conversation )
1237+ }
1238+
11871239 this . startInference ( ) ;
11881240 }
11891241 } ,
11901242 startInference : async function ( ) {
11911243 this . clear ( ) ;
1192- // Clear the input
1193- // Create a completion
1194- if ( this . conversation . length == 1 ) {
1195- historyStore . init ( this . conversation )
1196- }
11971244
11981245 // Image is too large, only latest query could be kept
11991246 const conversation = this . conversation . reduce ( ( newConversation , item ) => {
@@ -1362,7 +1409,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
13621409 } ) ;
13631410
13641411 const chatbotStore = useChatbotStore ( ) ;
1365- const snackbarStore = useSnackbarStore ( ) ;
1412+
13661413 const settingStore = useSettingStore ( ) ;
13671414 const agentStore = useAgentStore ( ) ;
13681415 const messageStore = useMessageStore ( ) ;
@@ -1540,7 +1587,9 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
15401587
15411588 watch ( computed ( ( ) => messageStore . conversation ) ,
15421589 ( newValue , oldValue ) => {
1543- asyncScrollToBottom ( )
1590+ if ( newValue [ newValue . length - 1 ] !== oldValue [ oldValue . length - 1 ] ) {
1591+ asyncScrollToBottom ( ) ;
1592+ }
15441593 } , { deep : true } ) ;
15451594
15461595 watch ( computed ( ( ) => messageStore . images ) ,
@@ -1760,6 +1809,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
17601809 stopped : 'Generating stopped.' ,
17611810 parseStreamFail : 'Cannot read the stream.' ,
17621811 parseConfigFail : 'Cannot parse the config file.' ,
1812+ copied : 'Copied to clipboard.'
17631813 }
17641814 } ,
17651815 } ,
@@ -1793,6 +1843,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
17931843 stopped : 'Generazione interrotta.' ,
17941844 parseStreamFail : 'Impossibile leggere il flusso.' ,
17951845 parseConfigFail : 'Impossibile analizzare il file di configurazione.' ,
1846+ copied : 'Copiato negli appunti.'
17961847 }
17971848 } ,
17981849 } ,
@@ -1817,6 +1868,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18171868 stopped : 'ビルドを停止します' ,
18181869 parseStreamFail : 'データフローを解決できませんでした' ,
18191870 parseConfigFail : '設定ファイルを解決できません' ,
1871+ copied : 'クリップボードにコピーされました'
18201872 }
18211873 } ,
18221874 } ,
@@ -1843,6 +1895,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18431895 stopped : 'Genereringen har stoppats.' ,
18441896 parseStreamFail : 'Det går inte att läsa strömmen.' ,
18451897 parseConfigFail : 'Det går inte att parsa konfigurationsfilen.' ,
1898+ copied : 'Kopierad till urklipp.'
18461899 }
18471900 } ,
18481901 } ,
@@ -1876,6 +1929,7 @@ <h5 class="font-weight-bold">{{ column.key }}</h5>
18761929 stopped : '停止生成' ,
18771930 parseStreamFail : '无法解析数据流' ,
18781931 parseConfigFail : '无法解析配置文件' ,
1932+ copied : '已复制到剪切板'
18791933 }
18801934 } ,
18811935 } ,
0 commit comments