@@ -235,6 +235,13 @@ func editModelfile(client *api.Client, modelName string) (string, error) {
235
235
// Write the fetched content to a temporary file
236
236
tempDir := os .TempDir ()
237
237
newModelfilePath := filepath .Join (tempDir , fmt .Sprintf ("%s_modelfile.txt" , modelName ))
238
+
239
+ // Ensure parent directories exist
240
+ parentDir := filepath .Dir (newModelfilePath )
241
+ if err := os .MkdirAll (parentDir , 0755 ); err != nil {
242
+ return "" , fmt .Errorf ("error creating directory for modelfile: %v" , err )
243
+ }
244
+
238
245
err = os .WriteFile (newModelfilePath , []byte (modelfileContent ), 0644 )
239
246
if err != nil {
240
247
return "" , fmt .Errorf ("error writing modelfile to temp file: %v" , err )
@@ -327,7 +334,7 @@ func editModelfile(client *api.Client, modelName string) (string, error) {
327
334
// log to the console if we're not in a tea app
328
335
fmt .Printf ("Model %s updated successfully\n " , modelName )
329
336
330
- return fmt .Sprintf ("Model %s updated successfully, Press 'q' to return to the models list " , modelName ), nil
337
+ return fmt .Sprintf ("Model %s updated successfully" , modelName ), nil
331
338
}
332
339
333
340
func isLocalhost (url string ) bool {
@@ -524,6 +531,78 @@ func (m *AppModel) startPullModel(modelName string) tea.Cmd {
524
531
}
525
532
}
526
533
534
+ // startPullModelPreserveConfig pulls a model while preserving user-modified configuration
535
+ func (m * AppModel ) startPullModelPreserveConfig (modelName string ) tea.Cmd {
536
+ return func () tea.Msg {
537
+ ctx := context .Background ()
538
+
539
+ // Step 1: Extract current parameters and template before pulling
540
+ logging .InfoLogger .Printf ("Extracting parameters for model %s before pulling" , modelName )
541
+ currentParams , currentTemplate , systemPrompt , err := getModelParamsWithSystem (modelName , m .client )
542
+ if err != nil {
543
+ logging .ErrorLogger .Printf ("Error extracting parameters for model %s: %v" , modelName , err )
544
+ return pullErrorMsg {fmt .Errorf ("failed to extract parameters: %v" , err )}
545
+ }
546
+
547
+ // Step 2: Pull the updated model
548
+ logging .InfoLogger .Printf ("Pulling updated model: %s" , modelName )
549
+ req := & api.PullRequest {Name : modelName }
550
+ err = m .client .Pull (ctx , req , func (resp api.ProgressResponse ) error {
551
+ m .pullProgress = float64 (resp .Completed ) / float64 (resp .Total )
552
+ return nil
553
+ })
554
+ if err != nil {
555
+ return pullErrorMsg {err }
556
+ }
557
+
558
+ // Step 3: Apply the saved configuration back to the updated model
559
+ logging .InfoLogger .Printf ("Restoring configuration for model: %s" , modelName )
560
+
561
+ // Create request with base fields
562
+ createReq := & api.CreateRequest {
563
+ Model : modelName , // The model to update
564
+ From : modelName , // Use the same model name as base (it's now been updated)
565
+ }
566
+
567
+ // Add template if it exists
568
+ if currentTemplate != "" {
569
+ createReq .Template = currentTemplate
570
+ }
571
+
572
+ // Add system prompt if it exists
573
+ if systemPrompt != "" {
574
+ createReq .System = systemPrompt
575
+ }
576
+
577
+ // Add parameters if any were found
578
+ if len (currentParams ) > 0 {
579
+ // Convert map[string]string to map[string]any
580
+ parameters := make (map [string ]any )
581
+ for k , v := range currentParams {
582
+ // Try to convert numeric values
583
+ if floatVal , err := strconv .ParseFloat (v , 64 ); err == nil {
584
+ parameters [k ] = floatVal
585
+ } else if intVal , err := strconv .Atoi (v ); err == nil {
586
+ parameters [k ] = intVal
587
+ } else {
588
+ parameters [k ] = v
589
+ }
590
+ }
591
+ createReq .Parameters = parameters
592
+ }
593
+
594
+ // Apply the configuration
595
+ err = m .client .Create (ctx , createReq , func (resp api.ProgressResponse ) error {
596
+ return nil
597
+ })
598
+ if err != nil {
599
+ return pullErrorMsg {fmt .Errorf ("failed to restore configuration: %v" , err )}
600
+ }
601
+
602
+ return pullSuccessMsg {modelName }
603
+ }
604
+ }
605
+
527
606
type editorFinishedMsg struct { err error }
528
607
529
608
func cleanupSymlinkedModels (lmStudioModelsDir string ) {
@@ -796,6 +875,80 @@ func extractTemplateAndSystem(content string) (template string, system string) {
796
875
return template , system
797
876
}
798
877
878
+ // getModelParamsWithSystem extracts parameters, template, and system prompt from a model's modelfile
879
+ func getModelParamsWithSystem (modelName string , client * api.Client ) (map [string ]string , string , string , error ) {
880
+ logging .InfoLogger .Printf ("Getting parameters and system prompt for model: %s\n " , modelName )
881
+ ctx := context .Background ()
882
+ req := & api.ShowRequest {Name : modelName }
883
+ resp , err := client .Show (ctx , req )
884
+ if err != nil {
885
+ logging .ErrorLogger .Printf ("Error getting modelfile for %s: %v\n " , modelName , err )
886
+ return nil , "" , "" , err
887
+ }
888
+ output := []byte (resp .Modelfile )
889
+ lines := strings .Split (strings .TrimSpace (string (output )), "\n " )
890
+ params := make (map [string ]string )
891
+ var template string
892
+ var system string
893
+
894
+ inTemplate := false
895
+ inMultilineTemplate := false
896
+ var templateLines []string
897
+
898
+ for _ , line := range lines {
899
+ trimmed := strings .TrimSpace (line )
900
+
901
+ // Handle TEMPLATE directive
902
+ if strings .HasPrefix (trimmed , "TEMPLATE" ) {
903
+ if strings .Contains (trimmed , `"""` ) {
904
+ // Multi-line template
905
+ templateContent := strings .TrimPrefix (trimmed , "TEMPLATE " )
906
+ templateContent = strings .TrimSpace (templateContent )
907
+ if strings .HasPrefix (templateContent , `"""` ) {
908
+ templateContent = strings .TrimPrefix (templateContent , `"""` )
909
+ }
910
+ inTemplate = true
911
+ inMultilineTemplate = true
912
+ if templateContent != "" {
913
+ templateLines = append (templateLines , templateContent )
914
+ }
915
+ } else {
916
+ // Single-line template
917
+ template = strings .TrimPrefix (trimmed , "TEMPLATE " )
918
+ template = strings .Trim (template , `"` )
919
+ }
920
+ } else if inTemplate {
921
+ if inMultilineTemplate && strings .HasSuffix (trimmed , `"""` ) {
922
+ line = strings .TrimSuffix (line , `"""` )
923
+ if line != "" {
924
+ templateLines = append (templateLines , line )
925
+ }
926
+ inTemplate = false
927
+ inMultilineTemplate = false
928
+ } else {
929
+ templateLines = append (templateLines , line )
930
+ }
931
+ } else if strings .HasPrefix (trimmed , "SYSTEM" ) {
932
+ system = strings .TrimPrefix (trimmed , "SYSTEM " )
933
+ // Remove surrounding quotes if present
934
+ system = strings .Trim (system , `"` )
935
+ } else if strings .HasPrefix (trimmed , "PARAMETER" ) {
936
+ parts := strings .SplitN (trimmed , " " , 3 )
937
+ if len (parts ) >= 3 {
938
+ key := parts [1 ]
939
+ value := strings .TrimSpace (parts [2 ])
940
+ params [key ] = value
941
+ }
942
+ }
943
+ }
944
+
945
+ if len (templateLines ) > 0 {
946
+ template = strings .Join (templateLines , "\n " )
947
+ }
948
+
949
+ return params , template , system , nil
950
+ }
951
+
799
952
// extractParameters extracts parameter values from modelfile content
800
953
func extractParameters (content string ) map [string ]any {
801
954
parameters := make (map [string ]any )
0 commit comments