@@ -4,13 +4,10 @@ import (
4
4
"fmt"
5
5
"os"
6
6
"path/filepath"
7
- "regexp"
8
- "strconv"
9
- "strings"
10
7
11
8
"github.com/gofiber/fiber/v2"
12
9
"github.com/mudler/LocalAI/core/config"
13
- "github.com/mudler/LocalAI/pkg/downloader "
10
+ "github.com/mudler/LocalAI/pkg/utils "
14
11
"gopkg.in/yaml.v3"
15
12
)
16
13
@@ -28,6 +25,14 @@ func GetEditModelPage(cl *config.BackendConfigLoader, appConfig *config.Applicat
28
25
29
26
// Load the existing configuration
30
27
configPath := filepath .Join (appConfig .ModelPath , modelName + ".yaml" )
28
+ if err := utils .InTrustedRoot (configPath , appConfig .ModelPath ); err != nil {
29
+ response := ModelResponse {
30
+ Success : false ,
31
+ Error : "Model configuration not trusted: " + err .Error (),
32
+ }
33
+ return c .Status (404 ).JSON (response )
34
+ }
35
+
31
36
if _ , err := os .Stat (configPath ); os .IsNotExist (err ) {
32
37
response := ModelResponse {
33
38
Success : false ,
@@ -56,7 +61,7 @@ func GetEditModelPage(cl *config.BackendConfigLoader, appConfig *config.Applicat
56
61
}
57
62
58
63
// Render the edit page with the current configuration
59
- return c .Render ("views/edit- model" , fiber.Map {
64
+ return c .Render ("views/model-editor " , fiber.Map {
60
65
"ModelName" : modelName ,
61
66
"Config" : backendConfig ,
62
67
})
@@ -73,11 +78,22 @@ func EditModelEndpoint(cl *config.BackendConfigLoader, appConfig *config.Applica
73
78
})
74
79
}
75
80
76
- var req ModelRequest
77
- if err := c .BodyParser (& req ); err != nil {
81
+ // Get the raw body as YAML
82
+ body := c .Body ()
83
+ if len (body ) == 0 {
78
84
response := ModelResponse {
79
85
Success : false ,
80
- Error : "Failed to parse request: " + err .Error (),
86
+ Error : "Request body is empty" ,
87
+ }
88
+ return c .Status (400 ).JSON (response )
89
+ }
90
+
91
+ // Unmarshal YAML directly into BackendConfig
92
+ var req config.BackendConfig
93
+ if err := yaml .Unmarshal (body , & req ); err != nil {
94
+ response := ModelResponse {
95
+ Success : false ,
96
+ Error : "Failed to parse YAML: " + err .Error (),
81
97
}
82
98
return c .Status (400 ).JSON (response )
83
99
}
@@ -93,6 +109,14 @@ func EditModelEndpoint(cl *config.BackendConfigLoader, appConfig *config.Applica
93
109
94
110
// Load the existing configuration
95
111
configPath := filepath .Join (appConfig .ModelPath , modelName + ".yaml" )
112
+ if err := utils .InTrustedRoot (configPath , appConfig .ModelPath ); err != nil {
113
+ response := ModelResponse {
114
+ Success : false ,
115
+ Error : "Model configuration not trusted: " + err .Error (),
116
+ }
117
+ return c .Status (404 ).JSON (response )
118
+ }
119
+
96
120
if _ , err := os .Stat (configPath ); os .IsNotExist (err ) {
97
121
response := ModelResponse {
98
122
Success : false ,
@@ -111,146 +135,25 @@ func EditModelEndpoint(cl *config.BackendConfigLoader, appConfig *config.Applica
111
135
return c .Status (500 ).JSON (response )
112
136
}
113
137
114
- var existingConfig config.BackendConfig
115
- if err := yaml .Unmarshal (configData , & existingConfig ); err != nil {
138
+ var backendConfig config.BackendConfig
139
+ if err := yaml .Unmarshal (configData , & backendConfig ); err != nil {
116
140
response := ModelResponse {
117
141
Success : false ,
118
142
Error : "Failed to parse existing configuration: " + err .Error (),
119
143
}
120
144
return c .Status (500 ).JSON (response )
121
145
}
122
146
123
- // Update the configuration with new values
124
- backendConfig := & existingConfig
125
- backendConfig .Backend = req .Backend
126
- backendConfig .Description = req .Description
127
- backendConfig .Usage = req .Usage
128
- backendConfig .Model = req .Model
129
- backendConfig .MMProj = req .MMProj
130
-
131
- // Set template configuration
132
- if req .ChatTemplate != "" || req .CompletionTemplate != "" {
133
- backendConfig .TemplateConfig = config.TemplateConfig {
134
- Chat : req .ChatTemplate ,
135
- Completion : req .CompletionTemplate ,
136
- }
137
- }
138
-
139
- // Set system prompt
140
- if req .SystemPrompt != "" {
141
- backendConfig .SystemPrompt = req .SystemPrompt
142
- }
143
-
144
- // Set prediction options
145
- if temp , err := strconv .ParseFloat (req .Temperature , 32 ); err == nil {
146
- backendConfig .Temperature = & temp
147
- }
148
- if topP , err := strconv .ParseFloat (req .TopP , 32 ); err == nil {
149
- backendConfig .TopP = & topP
150
- }
151
- if topK , err := strconv .Atoi (req .TopK ); err == nil {
152
- backendConfig .TopK = & topK
153
- }
154
- if ctxSize , err := strconv .Atoi (req .ContextSize ); err == nil {
155
- backendConfig .ContextSize = & ctxSize
156
- }
157
- if threads , err := strconv .Atoi (req .Threads ); err == nil {
158
- backendConfig .Threads = & threads
159
- }
160
- if seed , err := strconv .Atoi (req .Seed ); err == nil {
161
- backendConfig .Seed = & seed
162
- }
163
-
164
- // Set feature flags
165
- backendConfig .F16 = & req .F16
166
- backendConfig .CUDA = req .CUDA
167
- backendConfig .Embeddings = & req .Embeddings
168
- backendConfig .Debug = & req .Debug
169
- backendConfig .MMap = & req .MMap
170
- backendConfig .MMlock = & req .MMlock
171
-
172
- // Set backend-specific configurations
173
- switch req .Backend {
174
- case "llama.cpp" :
175
- if req .LoraAdapter != "" {
176
- backendConfig .LoraAdapter = req .LoraAdapter
177
- }
178
- if req .Grammar != "" {
179
- backendConfig .Grammar = req .Grammar
180
- }
181
- case "diffusers" :
182
- if req .PipelineType != "" {
183
- backendConfig .Diffusers .PipelineType = req .PipelineType
184
- }
185
- if req .SchedulerType != "" {
186
- backendConfig .Diffusers .SchedulerType = req .SchedulerType
187
- }
188
- case "whisper" :
189
- if req .AudioPath != "" {
190
- backendConfig .AudioPath = req .AudioPath
191
- }
192
- case "bark-cpp" :
193
- if req .Voice != "" {
194
- backendConfig .Voice = req .Voice
195
- }
196
- }
197
-
198
147
// Set defaults
199
148
backendConfig .SetDefaults ()
200
149
201
- // Check if model file is a URL and needs downloading
202
- if strings .HasPrefix (req .Model , "http" ) {
203
- // Add to download files
204
- backendConfig .DownloadFiles = append (backendConfig .DownloadFiles , config.File {
205
- URI : downloader .URI (req .Model ),
206
- })
207
- }
208
-
209
- if req .MMProj != "" && strings .HasPrefix (req .MMProj , "http" ) {
210
- // Add MMProj to download files
211
- backendConfig .DownloadFiles = append (backendConfig .DownloadFiles , config.File {
212
- URI : downloader .URI (req .MMProj ),
213
- })
214
- }
215
-
216
150
// Validate the configuration
217
151
if ! backendConfig .Validate () {
218
- // Provide more specific validation error messages
219
- var validationErrors []string
220
-
221
- if backendConfig .Backend == "" {
222
- validationErrors = append (validationErrors , "Backend name is required" )
223
- } else {
224
- // Check if backend name contains invalid characters
225
- re := regexp .MustCompile (`^[a-zA-Z0-9-_\.]+$` )
226
- if ! re .MatchString (backendConfig .Backend ) {
227
- validationErrors = append (validationErrors , "Backend name contains invalid characters (only letters, numbers, hyphens, underscores, and dots are allowed)" )
228
- }
229
- }
230
-
231
- if backendConfig .Model == "" {
232
- validationErrors = append (validationErrors , "Model file/URL is required" )
233
- }
234
-
235
- // Check for path traversal attempts
236
- if strings .Contains (backendConfig .Model , ".." ) || strings .Contains (backendConfig .Model , string (os .PathSeparator )) {
237
- validationErrors = append (validationErrors , "Model path contains invalid characters" )
238
- }
239
-
240
- if backendConfig .MMProj != "" {
241
- if strings .Contains (backendConfig .MMProj , ".." ) || strings .Contains (backendConfig .MMProj , string (os .PathSeparator )) {
242
- validationErrors = append (validationErrors , "MMProj path contains invalid characters" )
243
- }
244
- }
245
-
246
- if len (validationErrors ) == 0 {
247
- validationErrors = append (validationErrors , "Configuration validation failed" )
248
- }
249
152
250
153
response := ModelResponse {
251
154
Success : false ,
252
155
Error : "Validation failed" ,
253
- Details : validationErrors ,
156
+ Details : [] string { "Calling backend.Config.Validate()" } ,
254
157
}
255
158
return c .Status (400 ).JSON (response )
256
159
}
0 commit comments