126126 <!-- Config List -->
127127 <v-list lines =" two" >
128128 <v-list-item v-for =" config in configInfoList" :key =" config.id" :title =" config.name" >
129- <template v-slot :append v-if = " config . id !== ' default ' " >
129+ <template v-slot :append >
130130 <div class =" d-flex align-center" style =" gap : 8px ;" >
131+ <v-btn icon =" mdi-content-copy" size =" small" variant =" text" color =" primary"
132+ @click =" startCopyConfig(config)" ></v-btn >
131133 <v-btn icon =" mdi-pencil" size =" small" variant =" text" color =" warning"
134+ v-if =" config.id !== 'default'"
132135 @click =" startEditConfig(config)" ></v-btn >
133136 <v-btn icon =" mdi-delete" size =" small" variant =" text" color =" error"
137+ v-if =" config.id !== 'default'"
134138 @click =" confirmDeleteConfig(config)" ></v-btn >
135139 </div >
136140 </template >
141145 <v-divider v-if =" showConfigForm" class =" my-6" ></v-divider >
142146
143147 <div v-if =" showConfigForm" >
144- <h3 class =" mb-4" >{{ isEditingConfig ? tm('configManagement.editConfig') : tm('configManagement.newConfig') }}</h3 >
148+ <h3 class =" mb-4" >{{ configFormTitle }}</h3 >
145149
146150 <h4 >{{ tm('configManagement.configName') }}</h4 >
147151
151155 <div class =" d-flex justify-end mt-4" style =" gap : 8px ;" >
152156 <v-btn variant =" text" @click =" cancelConfigForm" >{{ tm('buttons.cancel') }}</v-btn >
153157 <v-btn color =" primary" @click =" saveConfigForm"
154- :disabled =" !configFormData.name " >
158+ :disabled =" isConfigFormSaveDisabled " >
155159 {{ isEditingConfig ? tm('buttons.update') : tm('buttons.create') }}
156160 </v-btn >
157161 </div >
@@ -297,6 +301,19 @@ export default {
297301 selectedConfigInfo () {
298302 return this .configInfoList .find (info => info .id === this .selectedConfigID ) || {};
299303 },
304+ configFormTitle () {
305+ if (this .isEditingConfig ) {
306+ return this .tm (' configManagement.editConfig' );
307+ }
308+ if (this .isCopyingConfig ) {
309+ return this .tm (' configManagement.copyConfig' );
310+ }
311+ return this .tm (' configManagement.newConfig' );
312+ },
313+ isConfigFormSaveDisabled () {
314+ const isNameEmpty = ! this .normalizeConfigName (this .configFormData .name );
315+ return isNameEmpty || (this .isCopyingConfig && ! this .copySourceConfigId );
316+ },
300317 configSelectItems () {
301318 const items = [... this .configInfoList ];
302319 items .push ({
@@ -343,6 +360,7 @@ export default {
343360 configManageDialog: false ,
344361 showConfigForm: false ,
345362 isEditingConfig: false ,
363+ isCopyingConfig: false ,
346364 config_data_has_changed: false ,
347365 config_data_str: " " ,
348366 config_data: {
@@ -371,6 +389,7 @@ export default {
371389 name: ' ' ,
372390 },
373391 editingConfigId: null ,
392+ copySourceConfigId: ' ' ,
374393
375394 // 测试聊天
376395 testChatDrawer: false ,
@@ -567,9 +586,9 @@ export default {
567586 this .save_message_snack = true ;
568587 }
569588 },
570- createNewConfig () {
589+ createNewConfig (configName ) {
571590 axios .post (' /api/config/abconf/new' , {
572- name: this . configFormData . name
591+ name: configName
573592 }).then ((res ) => {
574593 if (res .data .status === " ok" ) {
575594 this .save_message = res .data .message ;
@@ -589,6 +608,24 @@ export default {
589608 this .save_message_success = " error" ;
590609 });
591610 },
611+ normalizeConfigName (name ) {
612+ return typeof name === ' string' ? name .trim () : ' ' ;
613+ },
614+ hasDuplicateConfigName (name , excludeId = null ) {
615+ const normalizedName = this .normalizeConfigName (name);
616+ if (! normalizedName) {
617+ return false ;
618+ }
619+ return this .configInfoList .some ((config ) => {
620+ if (! config || ! config .name ) {
621+ return false ;
622+ }
623+ if (excludeId && config .id === excludeId) {
624+ return false ;
625+ }
626+ return this .normalizeConfigName (config .name ) === normalizedName;
627+ });
628+ },
592629 async onConfigSelect (value ) {
593630 if (value === ' _%manage%_' ) {
594631 this .configManageDialog = true ;
@@ -638,45 +675,92 @@ export default {
638675 }
639676 }
640677 },
678+ setConfigFormState ({ mode = ' create' , config = null , visible = true } = {}) {
679+ this .showConfigForm = visible;
680+ this .isEditingConfig = mode === ' edit' ;
681+ this .isCopyingConfig = mode === ' copy' ;
682+ this .editingConfigId = this .isEditingConfig && config ? config .id : null ;
683+ this .copySourceConfigId = this .isCopyingConfig && config ? config .id : ' ' ;
684+
685+ let name = ' ' ;
686+ if (this .isEditingConfig && config) {
687+ name = config .name || ' ' ;
688+ } else if (this .isCopyingConfig && config) {
689+ name = ` ${ config .name || ' ' } -copy` ;
690+ }
691+ this .configFormData = { name };
692+ },
641693 startCreateConfig () {
642- this .showConfigForm = true ;
643- this .isEditingConfig = false ;
644- this .configFormData = {
645- name: ' ' ,
646- };
647- this .editingConfigId = null ;
694+ this .setConfigFormState ({ mode: ' create' });
648695 },
649696 startEditConfig (config ) {
650- this .showConfigForm = true ;
651- this .isEditingConfig = true ;
652- this .editingConfigId = config .id ;
653-
654- this .configFormData = {
655- name: config .name || ' ' ,
656- };
697+ this .setConfigFormState ({ mode: ' edit' , config });
698+ },
699+ startCopyConfig (config ) {
700+ this .setConfigFormState ({ mode: ' copy' , config });
657701 },
658702 cancelConfigForm () {
659- this .showConfigForm = false ;
660- this .isEditingConfig = false ;
661- this .editingConfigId = null ;
662- this .configFormData = {
663- name: ' ' ,
664- };
703+ this .setConfigFormState ({ visible: false });
665704 },
666705 saveConfigForm () {
667- if (! this .configFormData .name ) {
706+ const normalizedName = this .normalizeConfigName (this .configFormData .name );
707+ if (! normalizedName) {
668708 this .save_message = this .tm (' configManagement.pleaseEnterName' );
669709 this .save_message_snack = true ;
670710 this .save_message_success = " error" ;
671711 return ;
672712 }
673-
713+ const excludeId = this .isEditingConfig ? this .editingConfigId : null ;
714+ if (this .hasDuplicateConfigName (normalizedName, excludeId)) {
715+ this .save_message = this .tm (' configManagement.nameExists' );
716+ this .save_message_snack = true ;
717+ this .save_message_success = " error" ;
718+ return ;
719+ }
720+ this .configFormData .name = normalizedName;
674721 if (this .isEditingConfig ) {
675- this .updateConfigInfo ();
722+ this .updateConfigInfo (normalizedName);
723+ } else if (this .isCopyingConfig ) {
724+ this .copyConfig (normalizedName);
676725 } else {
677- this .createNewConfig ();
726+ this .createNewConfig (normalizedName );
678727 }
679728 },
729+ copyConfig (configName ) {
730+ axios .get (' /api/config/abconf' , {
731+ params: { id: this .copySourceConfigId }
732+ }).then ((res ) => {
733+ const sourceConfig = res .data ? .data ? .config ;
734+ if (! sourceConfig) {
735+ this .save_message = this .tm (' configManagement.copyFailed' );
736+ this .save_message_snack = true ;
737+ this .save_message_success = " error" ;
738+ return ;
739+ }
740+ return axios .post (' /api/config/abconf/new' , {
741+ name: configName,
742+ config: sourceConfig
743+ });
744+ }).then ((res ) => {
745+ if (! res) return ;
746+ if (res .data .status === " ok" ) {
747+ this .save_message = res .data .message ;
748+ this .save_message_snack = true ;
749+ this .save_message_success = " success" ;
750+ this .getConfigInfoList (res .data .data .conf_id );
751+ this .cancelConfigForm ();
752+ } else {
753+ this .save_message = res .data .message ;
754+ this .save_message_snack = true ;
755+ this .save_message_success = " error" ;
756+ }
757+ }).catch ((err ) => {
758+ console .error (err);
759+ this .save_message = err? .response ? .data ? .message || this .tm (' configManagement.copyFailed' );
760+ this .save_message_snack = true ;
761+ this .save_message_success = " error" ;
762+ });
763+ },
680764 async confirmDeleteConfig (config ) {
681765 const message = this .tm (' configManagement.confirmDelete' ).replace (' {name}' , config .name );
682766 if (await askForConfirmationDialog (message, this .confirmDialog )) {
@@ -706,10 +790,10 @@ export default {
706790 this .save_message_success = " error" ;
707791 });
708792 },
709- updateConfigInfo () {
793+ updateConfigInfo (configName ) {
710794 axios .post (' /api/config/abconf/update' , {
711795 id: this .editingConfigId ,
712- name: this . configFormData . name
796+ name: configName
713797 }).then ((res ) => {
714798 if (res .data .status === " ok" ) {
715799 this .save_message = res .data .message ;
0 commit comments