mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-07 12:00:44 +08:00
fix(go): seed built-in agent templates for Go backend (#16666)
### Summary The Go backend never seeded the `canvas_template` table, so the "Create agent from template" page was blank when the frontend proxies to the Go API (`API_PROXY_SCHEME=go`). This PR adds `SeedCanvasTemplates()` in `internal/dao`, invoked from `InitDB()` after migrations, which loads `agent/templates/*.json` and mirrors Python's `add_graph_templates()` behavior. Changes: - Add `internal/dao/canvas_template_seed.go` to parse and upsert built-in templates. - Call `SeedCanvasTemplates()` in `InitDB()`. - Add `CanvasTypes` (`JSONSlice`) to `entity.CanvasTemplate` so the frontend can filter/group by category. - Skip seeding gracefully when the templates directory is absent. This fixes the blank template catalogue in Go mode.
This commit is contained in:
179
internal/dao/canvas_template_seed.go
Normal file
179
internal/dao/canvas_template_seed.go
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// 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 dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"ragflow/internal/common"
|
||||
"ragflow/internal/entity"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SeedCanvasTemplates seeds the canvas_template table from the built-in
|
||||
// agent/templates/*.json files. This mirrors Python's
|
||||
// init_data.add_graph_templates() so that the Go backend serves the same
|
||||
// template catalogue without relying on Python-side initialization.
|
||||
func SeedCanvasTemplates() error {
|
||||
dir := findAgentTemplatesDir()
|
||||
if dir == "" {
|
||||
common.Warn("Agent templates directory not found, skipping canvas template seeding")
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read agent templates directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
// Match Python's filter_delete([1 == 1]): start from a clean slate so
|
||||
// removed built-ins disappear and updated files take effect.
|
||||
if err := DB.Exec("DELETE FROM canvas_template").Error; err != nil {
|
||||
return fmt.Errorf("failed to clear canvas_template: %w", err)
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].Name() < entries[j].Name() })
|
||||
|
||||
var seeded int
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(dir, entry.Name())
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
common.Warn("Failed to read agent template", zap.String("file", path), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
tmpl, err := parseCanvasTemplateFile(raw)
|
||||
if err != nil {
|
||||
common.Warn("Failed to parse agent template", zap.String("file", path), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
if err := DB.Create(tmpl).Error; err != nil {
|
||||
common.Warn("Failed to save agent template", zap.String("file", path), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
seeded++
|
||||
}
|
||||
|
||||
common.Info("Seeded canvas templates", zap.Int("count", seeded), zap.String("dir", dir))
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCanvasTemplateFile(raw []byte) (*entity.CanvasTemplate, error) {
|
||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||
decoder.UseNumber()
|
||||
var data map[string]any
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmpl := &entity.CanvasTemplate{
|
||||
CanvasCategory: "agent_canvas",
|
||||
}
|
||||
|
||||
if v, ok := data["id"]; ok {
|
||||
tmpl.ID = fmt.Sprint(v)
|
||||
}
|
||||
if v, ok := data["title"].(map[string]any); ok {
|
||||
tmpl.Title = entity.JSONMap(v)
|
||||
}
|
||||
if v, ok := data["description"].(map[string]any); ok {
|
||||
tmpl.Description = entity.JSONMap(v)
|
||||
}
|
||||
if v, ok := data["avatar"].(string); ok && v != "" {
|
||||
tmpl.Avatar = &v
|
||||
}
|
||||
if v, ok := data["canvas_category"].(string); ok && v != "" {
|
||||
tmpl.CanvasCategory = v
|
||||
}
|
||||
|
||||
canvasTypes := collectCanvasTypes(data["canvas_type"], data["canvas_types"])
|
||||
if len(canvasTypes) > 0 {
|
||||
tmpl.CanvasTypes = canvasTypes
|
||||
if first, ok := canvasTypes[0].(string); ok {
|
||||
tmpl.CanvasType = &first
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := data["dsl"].(map[string]any); ok {
|
||||
tmpl.DSL = entity.JSONMap(v)
|
||||
}
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func collectCanvasTypes(rawType, rawTypes any) entity.JSONSlice {
|
||||
seen := make(map[string]struct{})
|
||||
var result entity.JSONSlice
|
||||
|
||||
add := func(s string) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[s]; ok {
|
||||
return
|
||||
}
|
||||
seen[s] = struct{}{}
|
||||
result = append(result, s)
|
||||
}
|
||||
|
||||
if s, ok := rawType.(string); ok {
|
||||
add(s)
|
||||
}
|
||||
|
||||
switch v := rawTypes.(type) {
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
if s, ok := item.(string); ok {
|
||||
add(s)
|
||||
}
|
||||
}
|
||||
case []string:
|
||||
for _, s := range v {
|
||||
add(s)
|
||||
}
|
||||
case string:
|
||||
add(v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func findAgentTemplatesDir() string {
|
||||
candidates := []string{
|
||||
"agent/templates",
|
||||
filepath.Join("..", "agent", "templates"),
|
||||
filepath.Join("..", "..", "agent", "templates"),
|
||||
filepath.Join("..", "..", "..", "agent", "templates"),
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
if info, err := os.Stat(candidate); err == nil && info.IsDir() {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -171,6 +171,13 @@ func InitDB() error {
|
||||
return fmt.Errorf("failed to run manual migrations: %w", err)
|
||||
}
|
||||
|
||||
// Seed built-in agent templates so the Go backend can serve the
|
||||
// "create agent from template" catalogue without relying on Python-side
|
||||
// initialization.
|
||||
if err = SeedCanvasTemplates(); err != nil {
|
||||
common.Warn("Failed to seed canvas templates", zap.Error(err))
|
||||
}
|
||||
|
||||
common.Info("Database connected and migrated successfully")
|
||||
|
||||
err = models.InitProviderManager("conf/models")
|
||||
|
||||
@@ -39,13 +39,14 @@ func (UserCanvas) TableName() string {
|
||||
|
||||
// CanvasTemplate canvas template model
|
||||
type CanvasTemplate struct {
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Avatar *string `gorm:"column:avatar;type:longtext" json:"avatar,omitempty"`
|
||||
Title JSONMap `gorm:"column:title;type:longtext" json:"title"`
|
||||
Description JSONMap `gorm:"column:description;type:longtext" json:"description"`
|
||||
CanvasType *string `gorm:"column:canvas_type;size:32;index" json:"canvas_type,omitempty"`
|
||||
CanvasCategory string `gorm:"column:canvas_category;size:32;not null;default:agent_canvas;index" json:"canvas_category"`
|
||||
DSL JSONMap `gorm:"column:dsl;type:longtext" json:"dsl,omitempty"`
|
||||
ID string `gorm:"column:id;primaryKey;size:32" json:"id"`
|
||||
Avatar *string `gorm:"column:avatar;type:longtext" json:"avatar,omitempty"`
|
||||
Title JSONMap `gorm:"column:title;type:longtext" json:"title"`
|
||||
Description JSONMap `gorm:"column:description;type:longtext" json:"description"`
|
||||
CanvasType *string `gorm:"column:canvas_type;size:32;index" json:"canvas_type,omitempty"`
|
||||
CanvasTypes JSONSlice `gorm:"column:canvas_types;type:longtext" json:"canvas_types,omitempty"`
|
||||
CanvasCategory string `gorm:"column:canvas_category;size:32;not null;default:agent_canvas;index" json:"canvas_category"`
|
||||
DSL JSONMap `gorm:"column:dsl;type:longtext" json:"dsl,omitempty"`
|
||||
BaseModel
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user