package app import ( "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "regexp" "strings" ) type KolVariableType string const ( KolVariableTypeInput KolVariableType = "input" KolVariableTypeTextarea KolVariableType = "textarea" KolVariableTypeSelect KolVariableType = "select" KolVariableTypeNumber KolVariableType = "number" KolVariableTypeCheckbox KolVariableType = "checkbox" ) type KolVariableDefinition struct { ID string `json:"id"` Key string `json:"key"` Label string `json:"label"` Type KolVariableType `json:"type"` Required bool `json:"required"` Placeholder *string `json:"placeholder,omitempty"` Options []string `json:"options,omitempty"` } type KolSchemaJSON struct { Variables []KolVariableDefinition `json:"variables"` } var kolPlaceholderRE = regexp.MustCompile(`\{\{\s*([^}\s]+)\s*\}\}`) func RenderPrompt(content string, schema KolSchemaJSON, values map[string]any) (string, error) { schema = normalizeKolSchema(schema) byID := make(map[string]KolVariableDefinition, len(schema.Variables)) for _, variable := range schema.Variables { byID[variable.ID] = variable } missingSet := make(map[string]struct{}) missing := make([]string, 0) addMissing := func(id string) { if id == "" { return } if _, exists := missingSet[id]; exists { return } missingSet[id] = struct{}{} missing = append(missing, id) } rendered := kolPlaceholderRE.ReplaceAllStringFunc(content, func(match string) string { submatches := kolPlaceholderRE.FindStringSubmatch(match) if len(submatches) < 2 { return match } id := submatches[1] variable, exists := byID[id] if !exists { addMissing(id) return match } value, hasValue := values[id] if hasValue { return fmt.Sprint(value) } if variable.Required { addMissing(id) } return "" }) if len(missing) > 0 { return "", fmt.Errorf("missing variables: %s", strings.Join(missing, ", ")) } return rendered, nil } func HashPrompt(rendered string) string { sum := sha256.Sum256([]byte(rendered)) return hex.EncodeToString(sum[:]) } func ValidateSchema(schema KolSchemaJSON) error { schema = normalizeKolSchema(schema) seen := make(map[string]struct{}, len(schema.Variables)) for _, variable := range schema.Variables { id := strings.TrimSpace(variable.ID) if id == "" || !strings.HasPrefix(id, "var_") { return fmt.Errorf("variable id must be non-empty and start with var_") } if _, exists := seen[id]; exists { return fmt.Errorf("duplicate variable id: %s", id) } seen[id] = struct{}{} switch KolVariableType(strings.TrimSpace(string(variable.Type))) { case KolVariableTypeInput, KolVariableTypeTextarea, KolVariableTypeSelect, KolVariableTypeNumber, KolVariableTypeCheckbox: default: return fmt.Errorf("unsupported variable type: %s", variable.Type) } } return nil } func JSONEncodeSchema(schema KolSchemaJSON) ([]byte, error) { return json.Marshal(normalizeKolSchema(schema)) } func normalizeKolSchema(schema KolSchemaJSON) KolSchemaJSON { if schema.Variables == nil { schema.Variables = []KolVariableDefinition{} } return schema }