Explorar o código

feat: support bundle README and tags

Yeuoly hai 8 meses
pai
achega
bb5bcc920b

+ 25 - 0
cmd/commandline/bundle/init.go

@@ -1,10 +1,13 @@
 package bundle
 
 import (
+	"bytes"
 	_ "embed"
 	"errors"
 	"os"
 	"path"
+	"text/template"
+	"time"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
@@ -17,6 +20,9 @@ import (
 //go:embed templates/icon.svg
 var BUNDLE_ICON []byte
 
+//go:embed templates/README.md
+var BUNDLE_README []byte
+
 func generateNewBundle() (*bundle_entities.Bundle, error) {
 	m := newProfile()
 	p := tea.NewProgram(m)
@@ -80,6 +86,25 @@ func InitBundle() {
 		return
 	}
 
+	// create README.md
+	tmpl := template.Must(template.New("README").Parse(string(BUNDLE_README)))
+	// render the template
+	var buf bytes.Buffer
+	if err := tmpl.Execute(&buf, map[string]interface{}{
+		"Author":  bundle.Author,
+		"Version": bundle.Version,
+		"Date":    time.Now().Format(time.DateOnly),
+	}); err != nil {
+		log.Error("Error rendering README template: %v", err)
+		return
+	}
+
+	// save README.md
+	if err := os.WriteFile(path.Join(bundleDir, "README.md"), buf.Bytes(), 0644); err != nil {
+		log.Error("Error saving README.md: %v", err)
+		return
+	}
+
 	// create _assets directory
 	if err := os.MkdirAll(path.Join(bundleDir, "_assets"), 0755); err != nil {
 		log.Error("Error creating _assets directory: %v", err)

+ 8 - 0
cmd/commandline/bundle/templates/README.md

@@ -0,0 +1,8 @@
+## Bundle
+
+**Author:** {{ .Author }}
+**Version:** {{ .Version }}
+**Date:** {{ .Date }}
+
+### Dependencies
+- Please leave your dependencies here, make everyone understand your bundle.

+ 5 - 0
internal/core/bundle_packager/bundle_packager.go

@@ -37,5 +37,10 @@ type BundlePackager interface {
 	BumpVersion(targetVersion manifest_entities.Version)
 
 	// FetchAsset fetches the asset of the bundle
+	// NOTE: path is the relative path to _assets folder
 	FetchAsset(path string) ([]byte, error)
+
+	// ReadFile reads the file from the bundle
+	// NOTE: path is the relative path to the root of the bundle
+	ReadFile(path string) ([]byte, error)
 }

+ 4 - 0
internal/core/bundle_packager/local.go

@@ -89,3 +89,7 @@ func (p *LocalBundlePackager) Save() error {
 
 	return nil
 }
+
+func (p *LocalBundlePackager) ReadFile(path string) ([]byte, error) {
+	return os.ReadFile(filepath.Join(p.path, path))
+}

+ 11 - 0
internal/core/bundle_packager/memory_zip.go

@@ -73,3 +73,14 @@ func NewMemoryZipBundlePackager(zipFile []byte) (*MemoryZipBundlePackager, error
 func (p *MemoryZipBundlePackager) Save() error {
 	return nil
 }
+
+func (p *MemoryZipBundlePackager) ReadFile(path string) ([]byte, error) {
+	// read the file from the zip reader
+	file, err := p.zipReader.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	return io.ReadAll(file)
+}

+ 38 - 0
internal/types/entities/bundle_entities/bundle.go

@@ -1,8 +1,11 @@
 package bundle_entities
 
 import (
+	"encoding/json"
+
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+	"gopkg.in/yaml.v3"
 )
 
 type Bundle struct {
@@ -14,4 +17,39 @@ type Bundle struct {
 	Author       string                             `json:"author" yaml:"author" validate:"required"`
 	Type         manifest_entities.DifyManifestType `json:"type" yaml:"type" validate:"required,eq=bundle"`
 	Dependencies []Dependency                       `json:"dependencies" yaml:"dependencies" validate:"required"`
+	Tags         []manifest_entities.PluginTag      `json:"tags" yaml:"tags" validate:"omitempty,dive,plugin_tag,max=128"`
+}
+
+// for api, avoid pydantic validation error
+func (b *Bundle) MarshalJSON() ([]byte, error) {
+	type alias Bundle
+	p := alias(*b)
+
+	if p.Tags == nil {
+		p.Tags = []manifest_entities.PluginTag{}
+	}
+
+	return json.Marshal(p)
+}
+
+// for unmarshal yaml
+func (b *Bundle) UnmarshalYAML(node *yaml.Node) error {
+	// avoid nil tags
+	type alias Bundle
+
+	p := &struct {
+		*alias `yaml:",inline"`
+	}{
+		alias: (*alias)(b),
+	}
+
+	if err := node.Decode(p); err != nil {
+		return err
+	}
+
+	if p.Tags == nil {
+		p.Tags = []manifest_entities.PluginTag{}
+	}
+
+	return nil
 }

+ 20 - 0
internal/types/entities/bundle_entities/bundle_test.go

@@ -0,0 +1,20 @@
+package bundle_entities
+
+import (
+	"testing"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
+
+func TestAvoidNilTags(t *testing.T) {
+	yaml := `name: test
+`
+	bundle, err := parser.UnmarshalYamlBytes[Bundle]([]byte(yaml))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if bundle.Tags == nil {
+		t.Fatal("tags should not be nil")
+	}
+}

+ 55 - 0
internal/types/entities/manifest_entities/tags.go

@@ -0,0 +1,55 @@
+package manifest_entities
+
+import (
+	"github.com/go-playground/validator/v10"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/validators"
+)
+
+type PluginTag string
+
+const (
+	PLUGIN_TAG_SEARCH        PluginTag = "search"
+	PLUGIN_TAG_IMAGE         PluginTag = "image"
+	PLUGIN_TAG_VIDEOS        PluginTag = "videos"
+	PLUGIN_TAG_WEATHER       PluginTag = "weather"
+	PLUGIN_TAG_FINANCE       PluginTag = "finance"
+	PLUGIN_TAG_DESIGN        PluginTag = "design"
+	PLUGIN_TAG_TRAVEL        PluginTag = "travel"
+	PLUGIN_TAG_SOCIAL        PluginTag = "social"
+	PLUGIN_TAG_NEWS          PluginTag = "news"
+	PLUGIN_TAG_MEDICAL       PluginTag = "medical"
+	PLUGIN_TAG_PRODUCTIVITY  PluginTag = "productivity"
+	PLUGIN_TAG_EDUCATION     PluginTag = "education"
+	PLUGIN_TAG_BUSINESS      PluginTag = "business"
+	PLUGIN_TAG_ENTERTAINMENT PluginTag = "entertainment"
+	PLUGIN_TAG_UTILITIES     PluginTag = "utilities"
+	PLUGIN_TAG_OTHER         PluginTag = "other"
+)
+
+func isPluginTag(fl validator.FieldLevel) bool {
+	value := fl.Field().String()
+	switch value {
+	case string(PLUGIN_TAG_SEARCH),
+		string(PLUGIN_TAG_IMAGE),
+		string(PLUGIN_TAG_VIDEOS),
+		string(PLUGIN_TAG_WEATHER),
+		string(PLUGIN_TAG_FINANCE),
+		string(PLUGIN_TAG_DESIGN),
+		string(PLUGIN_TAG_TRAVEL),
+		string(PLUGIN_TAG_SOCIAL),
+		string(PLUGIN_TAG_NEWS),
+		string(PLUGIN_TAG_MEDICAL),
+		string(PLUGIN_TAG_PRODUCTIVITY),
+		string(PLUGIN_TAG_EDUCATION),
+		string(PLUGIN_TAG_BUSINESS),
+		string(PLUGIN_TAG_ENTERTAINMENT),
+		string(PLUGIN_TAG_UTILITIES),
+		string(PLUGIN_TAG_OTHER):
+		return true
+	}
+	return false
+}
+
+func init() {
+	validators.GlobalEntitiesValidator.RegisterValidation("plugin_tag", isPluginTag)
+}

+ 3 - 52
internal/types/entities/plugin_entities/plugin_declaration.go

@@ -21,55 +21,6 @@ const (
 	PLUGIN_CATEGORY_EXTENSION PluginCategory = "extension"
 )
 
-type PluginTag string
-
-const (
-	PLUGIN_TAG_SEARCH        PluginTag = "search"
-	PLUGIN_TAG_IMAGE         PluginTag = "image"
-	PLUGIN_TAG_VIDEOS        PluginTag = "videos"
-	PLUGIN_TAG_WEATHER       PluginTag = "weather"
-	PLUGIN_TAG_FINANCE       PluginTag = "finance"
-	PLUGIN_TAG_DESIGN        PluginTag = "design"
-	PLUGIN_TAG_TRAVEL        PluginTag = "travel"
-	PLUGIN_TAG_SOCIAL        PluginTag = "social"
-	PLUGIN_TAG_NEWS          PluginTag = "news"
-	PLUGIN_TAG_MEDICAL       PluginTag = "medical"
-	PLUGIN_TAG_PRODUCTIVITY  PluginTag = "productivity"
-	PLUGIN_TAG_EDUCATION     PluginTag = "education"
-	PLUGIN_TAG_BUSINESS      PluginTag = "business"
-	PLUGIN_TAG_ENTERTAINMENT PluginTag = "entertainment"
-	PLUGIN_TAG_UTILITIES     PluginTag = "utilities"
-	PLUGIN_TAG_OTHER         PluginTag = "other"
-)
-
-func isPluginTag(fl validator.FieldLevel) bool {
-	value := fl.Field().String()
-	switch value {
-	case string(PLUGIN_TAG_SEARCH),
-		string(PLUGIN_TAG_IMAGE),
-		string(PLUGIN_TAG_VIDEOS),
-		string(PLUGIN_TAG_WEATHER),
-		string(PLUGIN_TAG_FINANCE),
-		string(PLUGIN_TAG_DESIGN),
-		string(PLUGIN_TAG_TRAVEL),
-		string(PLUGIN_TAG_SOCIAL),
-		string(PLUGIN_TAG_NEWS),
-		string(PLUGIN_TAG_MEDICAL),
-		string(PLUGIN_TAG_PRODUCTIVITY),
-		string(PLUGIN_TAG_EDUCATION),
-		string(PLUGIN_TAG_BUSINESS),
-		string(PLUGIN_TAG_ENTERTAINMENT),
-		string(PLUGIN_TAG_UTILITIES),
-		string(PLUGIN_TAG_OTHER):
-		return true
-	}
-	return false
-}
-
-func init() {
-	validators.GlobalEntitiesValidator.RegisterValidation("plugin_tag", isPluginTag)
-}
-
 type PluginPermissionRequirement struct {
 	Tool     *PluginPermissionToolRequirement     `json:"tool,omitempty" yaml:"tool,omitempty" validate:"omitempty"`
 	Model    *PluginPermissionModelRequirement    `json:"model,omitempty" yaml:"model,omitempty" validate:"omitempty"`
@@ -197,7 +148,7 @@ type PluginDeclarationWithoutAdvancedFields struct {
 	Resource    PluginResourceRequirement          `json:"resource" yaml:"resource,omitempty" validate:"required"`
 	Plugins     PluginExtensions                   `json:"plugins" yaml:"plugins,omitempty" validate:"required"`
 	Meta        PluginMeta                         `json:"meta" yaml:"meta,omitempty" validate:"required"`
-	Tags        []PluginTag                        `json:"tags" yaml:"tags,omitempty" validate:"omitempty,dive,plugin_tag,max=128"`
+	Tags        []manifest_entities.PluginTag      `json:"tags" yaml:"tags,omitempty" validate:"omitempty,dive,plugin_tag,max=128"`
 }
 
 func (p *PluginDeclarationWithoutAdvancedFields) UnmarshalJSON(data []byte) error {
@@ -213,7 +164,7 @@ func (p *PluginDeclarationWithoutAdvancedFields) UnmarshalJSON(data []byte) erro
 	}
 
 	if p.Tags == nil {
-		p.Tags = []PluginTag{}
+		p.Tags = []manifest_entities.PluginTag{}
 	}
 
 	return nil
@@ -321,7 +272,7 @@ func (p *PluginDeclaration) FillInDefaultValues() {
 	}
 
 	if p.Tags == nil {
-		p.Tags = []PluginTag{}
+		p.Tags = []manifest_entities.PluginTag{}
 	}
 }
 

+ 7 - 6
internal/types/entities/plugin_entities/tool_declaration.go

@@ -8,6 +8,7 @@ import (
 	ut "github.com/go-playground/universal-translator"
 	"github.com/go-playground/validator/v10"
 	en_translations "github.com/go-playground/validator/v10/translations/en"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/validators"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
 	"github.com/xeipuuv/gojsonschema"
@@ -115,12 +116,12 @@ func init() {
 }
 
 type ToolProviderIdentity struct {
-	Author      string      `json:"author" validate:"required"`
-	Name        string      `json:"name" validate:"required"`
-	Description I18nObject  `json:"description"`
-	Icon        string      `json:"icon" validate:"required"`
-	Label       I18nObject  `json:"label" validate:"required"`
-	Tags        []PluginTag `json:"tags" validate:"required,dive,plugin_tag"`
+	Author      string                        `json:"author" validate:"required"`
+	Name        string                        `json:"name" validate:"required"`
+	Description I18nObject                    `json:"description"`
+	Icon        string                        `json:"icon" validate:"required"`
+	Label       I18nObject                    `json:"label" validate:"required"`
+	Tags        []manifest_entities.PluginTag `json:"tags" validate:"required,dive,plugin_tag"`
 }
 
 type ToolProviderDeclaration struct {