@@ -6,6 +6,8 @@ import type { SharedContextRuntimeBackend } from '@shared/context-types.js';
66import { QWEN_MODEL_IDS } from '@shared/qwen-models.js' ;
77import {
88 DEFAULT_PRIMARY_CONTEXT_BACKEND ,
9+ getDefaultSharedContextModelForBackend ,
10+ isKnownSharedContextModelForBackend ,
911 SHARED_CONTEXT_RUNTIME_BACKENDS ,
1012 type SharedContextRuntimeConfigSnapshot ,
1113} from '@shared/shared-context-runtime-config.js' ;
@@ -140,6 +142,45 @@ const fieldInputStyle = {
140142 width : '100%' ,
141143} as const ;
142144
145+ const processingGridStyle = {
146+ display : 'grid' ,
147+ gridTemplateColumns : 'repeat(auto-fit, minmax(280px, 1fr))' ,
148+ gap : 12 ,
149+ alignItems : 'start' ,
150+ } as const ;
151+
152+ const processingCardStyle = {
153+ border : '1px solid #334155' ,
154+ borderRadius : 12 ,
155+ padding : 12 ,
156+ background : '#0f172a' ,
157+ display : 'flex' ,
158+ flexDirection : 'column' ,
159+ gap : 10 ,
160+ } as const ;
161+
162+ const backendChipRowStyle = {
163+ display : 'flex' ,
164+ gap : 8 ,
165+ flexWrap : 'wrap' ,
166+ } as const ;
167+
168+ function processingChipStyle ( active : boolean ) {
169+ return active
170+ ? {
171+ ...buttonStyle ,
172+ padding : '6px 10px' ,
173+ fontSize : 12 ,
174+ fontWeight : 700 ,
175+ }
176+ : {
177+ ...subtleButtonStyle ,
178+ padding : '6px 10px' ,
179+ fontSize : 12 ,
180+ fontWeight : 600 ,
181+ } ;
182+ }
183+
143184const defaultPolicyState : SharedProjectPolicy = {
144185 enrollmentId : '' ,
145186 enterpriseId : '' ,
@@ -162,6 +203,22 @@ const PROCESSING_MODEL_OPTIONS_BY_BACKEND: Record<SharedContextRuntimeBackend, r
162203 openclaw : [ ] ,
163204} ;
164205
206+ function resolveProcessingModelForBackend (
207+ nextBackend : SharedContextRuntimeBackend ,
208+ currentModel : string ,
209+ previousBackend ?: SharedContextRuntimeBackend ,
210+ ) : string {
211+ const trimmed = currentModel . trim ( ) ;
212+ if ( ! trimmed ) return getDefaultSharedContextModelForBackend ( nextBackend ) ;
213+ if ( previousBackend && trimmed === getDefaultSharedContextModelForBackend ( previousBackend ) ) {
214+ return getDefaultSharedContextModelForBackend ( nextBackend ) ;
215+ }
216+ if ( ! isKnownSharedContextModelForBackend ( nextBackend , trimmed ) ) {
217+ return getDefaultSharedContextModelForBackend ( nextBackend ) ;
218+ }
219+ return trimmed ;
220+ }
221+
165222type KindOption = SharedDocument [ 'kind' ] ;
166223type ManagementTab = 'enterprise' | 'members' | 'projects' | 'knowledge' | 'processing' ;
167224
@@ -417,6 +474,23 @@ export function SharedContextManagementPanel({ enterpriseId: initialEnterpriseId
417474 void reloadProcessingConfig ( ) ;
418475 } , [ activeTab , reloadProcessingConfig ] ) ;
419476
477+ const handleProcessingPrimaryBackendChange = useCallback ( ( nextBackend : SharedContextRuntimeBackend ) => {
478+ setProcessingPrimaryBackend ( ( prevBackend ) => {
479+ setProcessingPrimaryModel ( ( prevModel ) => resolveProcessingModelForBackend ( nextBackend , prevModel , prevBackend ) ) ;
480+ return nextBackend ;
481+ } ) ;
482+ } , [ ] ) ;
483+
484+ const handleProcessingBackupBackendChange = useCallback ( ( nextBackend : SharedContextRuntimeBackend ) => {
485+ setProcessingBackupBackend ( ( prevBackend ) => {
486+ setProcessingBackupModel ( ( prevModel ) => {
487+ if ( ! prevModel . trim ( ) ) return '' ;
488+ return resolveProcessingModelForBackend ( nextBackend , prevModel , prevBackend ) ;
489+ } ) ;
490+ return nextBackend ;
491+ } ) ;
492+ } , [ ] ) ;
493+
420494 return (
421495 < div style = { { display : 'flex' , flexDirection : 'column' , gap : 12 , padding : 8 , color : '#e2e8f0' , overflow : 'auto' } } >
422496 < div style = { sectionStyle } >
@@ -848,51 +922,67 @@ export function SharedContextManagementPanel({ enterpriseId: initialEnterpriseId
848922 < InfoCard title = { t ( 'sharedContext.management.processingModelTitle' ) } >
849923 { serverId ? (
850924 < >
851- < div style = { rowStyle } >
852- < label style = { { ...fieldLabelStyle , flex : '1 1 220px' } } >
853- < span > { t ( 'sharedContext.management.processingPrimaryBackend' ) } </ span >
854- < select
855- value = { processingPrimaryBackend }
856- onChange = { ( e ) => setProcessingPrimaryBackend ( ( e . currentTarget as HTMLSelectElement ) . value as SharedContextRuntimeBackend ) }
857- style = { fieldInputStyle }
858- >
859- { SHARED_CONTEXT_RUNTIME_BACKENDS . map ( ( backend ) => (
860- < option key = { backend } value = { backend } > { backend } </ option >
861- ) ) }
862- </ select >
863- </ label >
864- < label style = { { ...fieldLabelStyle , flex : '1 1 220px' } } >
865- < span > { t ( 'sharedContext.management.processingPrimaryModel' ) } </ span >
866- < input
867- list = { `shared-context-model-options-${ processingPrimaryBackend } ` }
868- value = { processingPrimaryModel }
869- onInput = { ( e ) => setProcessingPrimaryModel ( ( e . currentTarget as HTMLInputElement ) . value ) }
870- placeholder = { DEFAULT_PRIMARY_CONTEXT_MODEL }
871- style = { fieldInputStyle }
872- />
873- </ label >
874- < label style = { { ...fieldLabelStyle , flex : '1 1 220px' } } >
875- < span > { t ( 'sharedContext.management.processingBackupBackend' ) } </ span >
876- < select
877- value = { processingBackupBackend }
878- onChange = { ( e ) => setProcessingBackupBackend ( ( e . currentTarget as HTMLSelectElement ) . value as SharedContextRuntimeBackend ) }
879- style = { fieldInputStyle }
880- >
881- { SHARED_CONTEXT_RUNTIME_BACKENDS . map ( ( backend ) => (
882- < option key = { backend } value = { backend } > { backend } </ option >
883- ) ) }
884- </ select >
885- </ label >
886- < label style = { { ...fieldLabelStyle , flex : '1 1 220px' } } >
887- < span > { t ( 'sharedContext.management.processingBackupModel' ) } </ span >
888- < input
889- list = { `shared-context-model-options-${ processingBackupBackend } ` }
890- value = { processingBackupModel }
891- onInput = { ( e ) => setProcessingBackupModel ( ( e . currentTarget as HTMLInputElement ) . value ) }
892- placeholder = { t ( 'sharedContext.management.processingBackupPlaceholder' ) }
893- style = { fieldInputStyle }
894- />
895- </ label >
925+ < div style = { processingGridStyle } >
926+ < div style = { processingCardStyle } >
927+ < strong > { t ( 'sharedContext.management.processingPrimaryCardTitle' ) } </ strong >
928+ < label style = { fieldLabelStyle } >
929+ < span > { t ( 'sharedContext.management.processingPrimaryBackend' ) } </ span >
930+ < div style = { backendChipRowStyle } >
931+ { SHARED_CONTEXT_RUNTIME_BACKENDS . map ( ( backend ) => (
932+ < button
933+ key = { `primary:${ backend } ` }
934+ type = "button"
935+ aria-label = { `${ t ( 'sharedContext.management.processingPrimaryBackend' ) } : ${ backend } ` }
936+ style = { processingChipStyle ( processingPrimaryBackend === backend ) }
937+ onClick = { ( ) => handleProcessingPrimaryBackendChange ( backend ) }
938+ >
939+ { backend }
940+ </ button >
941+ ) ) }
942+ </ div >
943+ </ label >
944+ < label style = { fieldLabelStyle } >
945+ < span > { t ( 'sharedContext.management.processingPrimaryModel' ) } </ span >
946+ < input
947+ aria-label = { t ( 'sharedContext.management.processingPrimaryModel' ) }
948+ list = { `shared-context-model-options-${ processingPrimaryBackend } ` }
949+ value = { processingPrimaryModel }
950+ onInput = { ( e ) => setProcessingPrimaryModel ( ( e . currentTarget as HTMLInputElement ) . value ) }
951+ placeholder = { DEFAULT_PRIMARY_CONTEXT_MODEL }
952+ style = { fieldInputStyle }
953+ />
954+ </ label >
955+ </ div >
956+ < div style = { processingCardStyle } >
957+ < strong > { t ( 'sharedContext.management.processingBackupCardTitle' ) } </ strong >
958+ < label style = { fieldLabelStyle } >
959+ < span > { t ( 'sharedContext.management.processingBackupBackend' ) } </ span >
960+ < div style = { backendChipRowStyle } >
961+ { SHARED_CONTEXT_RUNTIME_BACKENDS . map ( ( backend ) => (
962+ < button
963+ key = { `backup:${ backend } ` }
964+ type = "button"
965+ aria-label = { `${ t ( 'sharedContext.management.processingBackupBackend' ) } : ${ backend } ` }
966+ style = { processingChipStyle ( processingBackupBackend === backend ) }
967+ onClick = { ( ) => handleProcessingBackupBackendChange ( backend ) }
968+ >
969+ { backend }
970+ </ button >
971+ ) ) }
972+ </ div >
973+ </ label >
974+ < label style = { fieldLabelStyle } >
975+ < span > { t ( 'sharedContext.management.processingBackupModel' ) } </ span >
976+ < input
977+ aria-label = { t ( 'sharedContext.management.processingBackupModel' ) }
978+ list = { `shared-context-model-options-${ processingBackupBackend } ` }
979+ value = { processingBackupModel }
980+ onInput = { ( e ) => setProcessingBackupModel ( ( e . currentTarget as HTMLInputElement ) . value ) }
981+ placeholder = { t ( 'sharedContext.management.processingBackupPlaceholder' ) }
982+ style = { fieldInputStyle }
983+ />
984+ </ label >
985+ </ div >
896986 </ div >
897987 { SHARED_CONTEXT_RUNTIME_BACKENDS . map ( ( backend ) => (
898988 < datalist id = { `shared-context-model-options-${ backend } ` } key = { backend } >
0 commit comments