// // Copyright 2026 The InfiniFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Package component — UserFillUp component (T3, plan §2.11.3 row 2). // // UserFillUp is the user-interaction / form-filling node. It renders an // optional `tips` template (with `{{key}}` placeholders resolved against // the form's input map) and passes each form field through to its // downstream outputs. File-type inputs (value.type starts with "file") // are deferred — Phase 5 will wire FileService.get_files; the P3 port // emits a stable "" stub so the run keeps flowing. // // Mirrors agent/component/fillup.py:24-83. package component import ( "context" "fmt" "regexp" "strings" "ragflow/internal/agent/runtime" ) const componentNameUserFillUp = "UserFillUp" // defaultUserFillUpTips is used when the operator omits the `tips` param. // Matches the Python default in fillup.py:28. const defaultUserFillUpTips = "Please fill up the form" // fileStubPrefix is prepended to the form-field key when an input is // classified as a file-type input. Phase 5 will replace this with the // actual FileService.get_files payload. const fileStubPrefix = "" stubs — FileService integration is Phase 5. Plain // string fields use their value verbatim; non-string values are // coerced via fmt.Sprintf("%v", ...). func renderTips(template string, fields map[string]any) string { if template == "" { return "" } return tipsPlaceholderPattern.ReplaceAllStringFunc(template, func(match string) string { sub := tipsPlaceholderPattern.FindStringSubmatch(match) if len(sub) < 2 { return match } key := sub[1] raw, ok := fields[key] if !ok { return "" } return fieldValueToString(key, raw) }) } // resolveFieldValue converts one form-field payload into the value // that should appear in the component's output map. // // Rules (mirroring fillup.py:69-79): // - dict with type starting with "file" → "" stub // - dict with optional=true and value==nil → nil // - dict with a `value` field → the inner value // - anything else → pass through unchanged func resolveFieldValue(key string, raw any) any { m, ok := raw.(map[string]any) if !ok { return raw } if isFileType(m) { return fileStubPrefix + key + ">" } if opt, _ := m["optional"].(bool); opt { if v, present := m["value"]; !present || v == nil { return nil } } if v, present := m["value"]; present { return v } return m } // isFileType reports whether the form-field payload's `type` field // starts with "file" (case-insensitive). Matches fillup.py:69's // `v.get("type", "").lower().find("file") >= 0` test. func isFileType(m map[string]any) bool { t, _ := m["type"].(string) return strings.HasPrefix(strings.ToLower(t), "file") } // fieldValueToString is the tips-substitution variant of // resolveFieldValue: it returns a string suitable for direct insertion // into the rendered template. File stubs are emitted here too so the // tips template can reference a file field without crashing. func fieldValueToString(key string, raw any) string { if m, ok := raw.(map[string]any); ok { if isFileType(m) { return fileStubPrefix + key + ">" } if v, present := m["value"]; present { return stringifyField(v) } return "" } return stringifyField(raw) } // stringifyField renders a single form-field value as a string for // template substitution. nil → "", strings stay verbatim, everything // else uses %v. func stringifyField(v any) string { if v == nil { return "" } if s, ok := v.(string); ok { return s } return fmt.Sprintf("%v", v) } // init registers UserFillUp with the orchestrator-owned registry. func init() { Register(componentNameUserFillUp, func(params map[string]any) (Component, error) { var p userFillUpParam if err := p.Update(params); err != nil { return nil, err } return NewUserFillUpComponent(p), nil }) }