feat: AI models admin, settings import, Anthropic adapter and the rest of AIService
- golem15_golem_models (encrypted write-only api_key) with its cabana list and form under golem15.golem.access_settings, and the Settings lookups (Models, DefaultModel with PHP's position-0 quirk, Vision, Image, File, ByName), every one Trusted - golem:import-settings copies the WinterCMS golem_settings repeater once - AnthropicAdapter; SendStream, SendFile/SendFilePath/SendFileReader, SendWithModel, SendToImageModel, SendToVisionModel, GenerateImage, Ask - PromptFactory; README documents the config keys, the environment variable, the command and the admin screen
This commit is contained in:
90
controllers/models_admin_controller.go
Normal file
90
controllers/models_admin_controller.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.golem15.com/golem15/sm-golem-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
)
|
||||
|
||||
// PermissionAccessSettings guards the AI models screen.
|
||||
const PermissionAccessSettings = "golem15.golem.access_settings"
|
||||
|
||||
// ModelsAdminController is the AI models list and form
|
||||
// (golem15.golem.models). The API key is write-only: the form shows it
|
||||
// redacted, and an update that sends it back empty or redacted keeps the
|
||||
// stored key.
|
||||
type ModelsAdminController struct{}
|
||||
|
||||
var (
|
||||
_ pact.AdminController = ModelsAdminController{}
|
||||
_ pact.AdminPermissioned = ModelsAdminController{}
|
||||
_ pact.AdminRecordSource = ModelsAdminController{}
|
||||
_ pact.FormBeforeCreate = ModelsAdminController{}
|
||||
_ pact.FormBeforeUpdate = ModelsAdminController{}
|
||||
)
|
||||
|
||||
// ID is golem15.golem.models.
|
||||
func (ModelsAdminController) ID() string { return "golem15.golem.models" }
|
||||
|
||||
// ModelName is the model's WinterCMS class name.
|
||||
func (ModelsAdminController) ModelName() string { return `Golem15\Golem\Models\AiModel` }
|
||||
|
||||
// ConfigDir holds config_list.yaml and config_form.yaml.
|
||||
func (ModelsAdminController) ConfigDir() string { return "controllers/aimodels" }
|
||||
|
||||
// RequiredPermissions is golem15.golem.access_settings.
|
||||
func (ModelsAdminController) RequiredPermissions() []string {
|
||||
return []string{PermissionAccessSettings}
|
||||
}
|
||||
|
||||
// NewRecord returns an empty AI model.
|
||||
func (ModelsAdminController) NewRecord() any { return &models.AiModel{} }
|
||||
|
||||
// redacted is how lagoon.Encrypted serializes, so how the form shows the
|
||||
// stored key.
|
||||
const redacted = "[redacted]"
|
||||
|
||||
func keyMissing(m *models.AiModel) bool {
|
||||
k := m.APIKey.Reveal()
|
||||
return k == "" || k == redacted
|
||||
}
|
||||
|
||||
// FormBeforeCreate requires an API key.
|
||||
func (ModelsAdminController) FormBeforeCreate(_ context.Context, model any) error {
|
||||
m, ok := model.(*models.AiModel)
|
||||
if !ok {
|
||||
return errors.New("golem: unexpected admin record")
|
||||
}
|
||||
if keyMissing(m) {
|
||||
return &cabana.ValidationError{Details: map[string]any{"api_key": []string{"The api key field is required."}}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormBeforeUpdate keeps the stored key when the form sent it back empty
|
||||
// or redacted.
|
||||
func (ModelsAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
|
||||
m, ok := model.(*models.AiModel)
|
||||
if !ok {
|
||||
return errors.New("golem: unexpected admin record")
|
||||
}
|
||||
if !keyMissing(m) {
|
||||
return nil
|
||||
}
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return errors.New("golem: no transaction for the model update")
|
||||
}
|
||||
var stored models.AiModel
|
||||
if err := tx.Select("api_key").Where("id = ?", m.ID).Take(&stored).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
m.APIKey = stored.APIKey
|
||||
if keyMissing(m) {
|
||||
return &cabana.ValidationError{Details: map[string]any{"api_key": []string{"The api key field is required."}}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user