소스 검색

feat: cli supports tool template

Yeuoly 8 달 전
부모
커밋
df62cc26e4

+ 73 - 0
cmd/commandline/init/category.go

@@ -0,0 +1,73 @@
+package init
+
+import (
+	"fmt"
+
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+type category struct {
+	cursor int
+}
+
+var categories = []string{
+	"tool",
+	"model",
+	"extension",
+}
+
+func newCategory() category {
+	return category{
+		// default category is tool
+		cursor: 0,
+	}
+}
+
+func (c category) Category() string {
+	return categories[c.cursor]
+}
+
+func (c category) View() string {
+	s := "Select the type of plugin you want to create\n"
+	for i, category := range categories {
+		if i == c.cursor {
+			s += fmt.Sprintf("\033[32m-> %s\033[0m\n", category)
+		} else {
+			s += fmt.Sprintf("  %s\n", category)
+		}
+	}
+	return s
+}
+
+func (c category) Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.String() {
+		case "ctrl+c", "q":
+			return c, SUB_MENU_EVENT_NONE, tea.Quit
+		case "j", "down":
+			c.cursor++
+			if c.cursor >= len(categories) {
+				c.cursor = len(categories) - 1
+			}
+		case "k", "up":
+			c.cursor--
+			if c.cursor < 0 {
+				c.cursor = 0
+			}
+		case "enter":
+			if c.cursor != 0 {
+				c.cursor = 0
+				return c, SUB_MENU_EVENT_NONE, nil
+			}
+
+			return c, SUB_MENU_EVENT_NEXT, nil
+		}
+	}
+
+	return c, SUB_MENU_EVENT_NONE, nil
+}
+
+func (c category) Init() tea.Cmd {
+	return nil
+}

+ 21 - 6
cmd/commandline/init/init.go

@@ -39,6 +39,7 @@ type subMenuKey string
 const (
 	SUB_MENU_KEY_PROFILE    subMenuKey = "profile"
 	SUB_MENU_KEY_LANGUAGE   subMenuKey = "language"
+	SUB_MENU_KEY_CATEGORY   subMenuKey = "category"
 	SUB_MENU_KEY_PERMISSION subMenuKey = "permission"
 )
 
@@ -55,6 +56,7 @@ func initialize() model {
 	m.subMenus = map[subMenuKey]subMenu{
 		SUB_MENU_KEY_PROFILE:    newProfile(),
 		SUB_MENU_KEY_LANGUAGE:   newLanguage(),
+		SUB_MENU_KEY_CATEGORY:   newCategory(),
 		SUB_MENU_KEY_PERMISSION: newPermission(),
 	}
 	m.currentSubMenu = SUB_MENU_KEY_PROFILE
@@ -114,11 +116,14 @@ func (m model) createPlugin() {
 
 	manifest := &plugin_entities.PluginDeclaration{
 		PluginDeclarationWithoutAdvancedFields: plugin_entities.PluginDeclarationWithoutAdvancedFields{
-			Version:   "0.0.1",
-			Type:      plugin_entities.PluginType,
-			Icon:      "icon.svg",
-			Author:    m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Author(),
-			Name:      m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
+			Version: "0.0.1",
+			Type:    plugin_entities.PluginType,
+			Icon:    "icon.svg",
+			Author:  m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Author(),
+			Name:    m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
+			Description: plugin_entities.I18nObject{
+				EnUS: m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Description(),
+			},
 			CreatedAt: time.Now(),
 			Resource: plugin_entities.PluginResourceRequirement{
 				Memory:     1024 * 1024 * 256, // 256MB
@@ -130,6 +135,11 @@ func (m model) createPlugin() {
 		},
 	}
 
+	category_string := m.subMenus[SUB_MENU_KEY_CATEGORY].(category).Category()
+	if category_string == "tool" {
+		manifest.Plugins.Tools = []string{fmt.Sprintf("provider/%s.yaml", manifest.Name)}
+	}
+
 	manifest.Meta = plugin_entities.PluginMeta{
 		Version: "0.0.1",
 		Arch: []constants.Arch{
@@ -192,7 +202,12 @@ func (m model) createPlugin() {
 		return
 	}
 
-	err = createPythonEnvironment(plugin_dir, manifest.Meta.Runner.Entrypoint)
+	err = createPythonEnvironment(
+		plugin_dir,
+		manifest.Meta.Runner.Entrypoint,
+		manifest,
+		m.subMenus[SUB_MENU_KEY_CATEGORY].(category).Category(),
+	)
 	if err != nil {
 		log.Error("failed to create python environment: %s", err)
 		return

+ 12 - 3
cmd/commandline/init/profile.go

@@ -27,8 +27,13 @@ func newProfile() profile {
 	author.CharLimit = 128
 	author.Prompt = "Author (press Enter to next step): "
 
+	description := ti.New()
+	description.Placeholder = "Description"
+	description.CharLimit = 1024
+	description.Prompt = "Description (press Enter to next step): "
+
 	return profile{
-		inputs: []ti.Model{name, author},
+		inputs: []ti.Model{name, author, description},
 	}
 }
 
@@ -40,8 +45,12 @@ func (p profile) Author() string {
 	return p.inputs[1].Value()
 }
 
+func (p profile) Description() string {
+	return p.inputs[2].Value()
+}
+
 func (p profile) View() string {
-	s := fmt.Sprintf("Edit profile of the plugin\n%s\n%s\n", p.inputs[0].View(), p.inputs[1].View())
+	s := fmt.Sprintf("Edit profile of the plugin\n%s\n%s\n%s\n", p.inputs[0].View(), p.inputs[1].View(), p.inputs[2].View())
 	if p.warning != "" {
 		s += fmt.Sprintf("\033[31m%s\033[0m\n", p.warning)
 	}
@@ -50,7 +59,7 @@ func (p profile) View() string {
 
 func (p *profile) checkRule() bool {
 	if p.inputs[p.cursor].Value() == "" {
-		p.warning = "Name and author cannot be empty"
+		p.warning = "Name, author and description cannot be empty"
 		return false
 	} else if p.cursor == 0 && !plugin_entities.PluginNameRegex.MatchString(p.inputs[p.cursor].Value()) {
 		p.warning = "Plugin name must be 1-128 characters long, and can only contain letters, numbers, dashes and underscores"

+ 114 - 1
cmd/commandline/init/python.go

@@ -5,6 +5,9 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
 )
 
 //go:embed templates/python/main.py
@@ -13,7 +16,23 @@ var PYTHON_ENTRYPOINT_TEMPLATE []byte
 //go:embed templates/python/requirements.txt
 var PYTHON_REQUIREMENTS_TEMPLATE []byte
 
-func createPythonEnvironment(root string, entrypoint string) error {
+//go:embed templates/python/tool_provider.yaml
+var PYTHON_TOOL_PROVIDER_TEMPLATE []byte
+
+//go:embed templates/python/tool.yaml
+var PYTHON_TOOL_TEMPLATE []byte
+
+//go:embed templates/python/tool.py
+var PYTHON_TOOL_PY_TEMPLATE []byte
+
+//go:embed templates/python/tool_provider.py
+var PYTHON_TOOL_PROVIDER_PY_TEMPLATE []byte
+
+func createPythonEnvironment(
+	root string, entrypoint string, manifest *plugin_entities.PluginDeclaration, category string,
+) error {
+	// TODO: enhance to use template renderer
+
 	// create the python environment
 	entrypoint_file_path := filepath.Join(root, fmt.Sprintf("%s.py", entrypoint))
 	if err := os.WriteFile(entrypoint_file_path, PYTHON_ENTRYPOINT_TEMPLATE, 0o644); err != nil {
@@ -25,5 +44,99 @@ func createPythonEnvironment(root string, entrypoint string) error {
 		return err
 	}
 
+	if category == "tool" {
+		if err := createPythonTool(root, manifest); err != nil {
+			return err
+		}
+
+		if err := createPythonToolProvider(root, manifest); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func createPythonTool(root string, manifest *plugin_entities.PluginDeclaration) error {
+	// create the tool
+	tool_dir := filepath.Join(root, "tools")
+	if err := os.MkdirAll(tool_dir, 0o755); err != nil {
+		return err
+	}
+	// replace the plugin name/author/description in the template
+	tool_file_content := strings.ReplaceAll(
+		string(PYTHON_TOOL_PY_TEMPLATE), "{{plugin_name}}", manifest.Name,
+	)
+	tool_file_content = strings.ReplaceAll(
+		tool_file_content, "{{author}}", manifest.Author,
+	)
+	tool_file_content = strings.ReplaceAll(
+		tool_file_content, "{{plugin_description}}", manifest.Description.EnUS,
+	)
+	tool_file_path := filepath.Join(tool_dir, fmt.Sprintf("%s.py", manifest.Name))
+	if err := os.WriteFile(tool_file_path, []byte(tool_file_content), 0o644); err != nil {
+		return err
+	}
+
+	// create the tool manifest
+	tool_manifest_file_path := filepath.Join(tool_dir, fmt.Sprintf("%s.yaml", manifest.Name))
+	if err := os.WriteFile(tool_manifest_file_path, PYTHON_TOOL_TEMPLATE, 0o644); err != nil {
+		return err
+	}
+	tool_manifest_file_content := strings.ReplaceAll(
+		string(PYTHON_TOOL_TEMPLATE), "{{plugin_name}}", manifest.Name,
+	)
+	tool_manifest_file_content = strings.ReplaceAll(
+		tool_manifest_file_content, "{{author}}", manifest.Author,
+	)
+	tool_manifest_file_content = strings.ReplaceAll(
+		tool_manifest_file_content, "{{plugin_description}}", manifest.Description.EnUS,
+	)
+	if err := os.WriteFile(tool_manifest_file_path, []byte(tool_manifest_file_content), 0o644); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func createPythonToolProvider(root string, manifest *plugin_entities.PluginDeclaration) error {
+	// create the tool provider
+	tool_provider_dir := filepath.Join(root, "provider")
+	if err := os.MkdirAll(tool_provider_dir, 0o755); err != nil {
+		return err
+	}
+	// replace the plugin name/author/description in the template
+	tool_provider_file_content := strings.ReplaceAll(
+		string(PYTHON_TOOL_PROVIDER_PY_TEMPLATE), "{{plugin_name}}", manifest.Name,
+	)
+	tool_provider_file_content = strings.ReplaceAll(
+		tool_provider_file_content, "{{author}}", manifest.Author,
+	)
+	tool_provider_file_content = strings.ReplaceAll(
+		tool_provider_file_content, "{{plugin_description}}", manifest.Description.EnUS,
+	)
+	tool_provider_file_path := filepath.Join(tool_provider_dir, fmt.Sprintf("%s.py", manifest.Name))
+	if err := os.WriteFile(tool_provider_file_path, []byte(tool_provider_file_content), 0o644); err != nil {
+		return err
+	}
+
+	// create the tool provider manifest
+	tool_provider_manifest_file_path := filepath.Join(tool_provider_dir, fmt.Sprintf("%s.yaml", manifest.Name))
+	if err := os.WriteFile(tool_provider_manifest_file_path, PYTHON_TOOL_PROVIDER_TEMPLATE, 0o644); err != nil {
+		return err
+	}
+	tool_provider_manifest_file_content := strings.ReplaceAll(
+		string(PYTHON_TOOL_PROVIDER_TEMPLATE), "{{plugin_name}}", manifest.Name,
+	)
+	tool_provider_manifest_file_content = strings.ReplaceAll(
+		tool_provider_manifest_file_content, "{{author}}", manifest.Author,
+	)
+	tool_provider_manifest_file_content = strings.ReplaceAll(
+		tool_provider_manifest_file_content, "{{plugin_description}}", manifest.Description.EnUS,
+	)
+	if err := os.WriteFile(tool_provider_manifest_file_path, []byte(tool_provider_manifest_file_content), 0o644); err != nil {
+		return err
+	}
+
 	return nil
 }

+ 1 - 1
cmd/commandline/init/templates/python/requirements.txt

@@ -1 +1 @@
-dify_plugin~=0.0.1
+dify_plugin

+ 11 - 0
cmd/commandline/init/templates/python/tool.py

@@ -0,0 +1,11 @@
+from collections.abc import Generator
+from typing import Any
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+class {{plugin_name}}Tool(Tool):
+    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+        yield self.create_json_message({
+            "result": "Hello, world!"
+        })

+ 30 - 0
cmd/commandline/init/templates/python/tool.yaml

@@ -0,0 +1,30 @@
+identity:
+  name: {{plugin_name}}
+  author: {{author}}
+  label:
+    en_US: {{plugin_name}}
+    zh_Hans: {{plugin_name}}
+    pt_BR: {{plugin_name}}
+description:
+  human:
+    en_US: {{plugin_description}}
+    zh_Hans: {{plugin_description}}
+    pt_BR: {{plugin_description}}
+  llm: {{plugin_description}}
+parameters:
+  - name: query
+    type: string
+    required: true
+    label:
+      en_US: Query string
+      zh_Hans: 查询语句
+      pt_BR: Query string
+    human_description:
+      en_US: {{plugin_description}}
+      zh_Hans: {{plugin_description}}
+      pt_BR: {{plugin_description}}
+    llm_description: {{plugin_description}}
+    form: llm
+extra:
+  python:
+    source: tools/{{plugin_name}}.py

+ 14 - 0
cmd/commandline/init/templates/python/tool_provider.py

@@ -0,0 +1,14 @@
+from typing import Any
+
+from dify_plugin import ToolProvider
+from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+
+
+class {{plugin_name}}Provider(ToolProvider):
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        try:
+            """
+            IMPLEMENT YOUR VALIDATION HERE
+            """
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

+ 17 - 0
cmd/commandline/init/templates/python/tool_provider.yaml

@@ -0,0 +1,17 @@
+identity:
+  author: {{author}}
+  name: {{plugin_name}}
+  label:
+    en_US: {{plugin_name}}
+    zh_Hans: {{plugin_name}}
+    pt_BR: {{plugin_name}}
+  description:
+    en_US: {{plugin_description}}
+    zh_Hans: {{plugin_description}}
+    pt_BR: {{plugin_description}}
+  icon: icon.svg
+tools:
+  - tools/{{plugin_name}}.yaml
+extra:
+  python:
+    source: provider/{{plugin_name}}.py