init.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package plugin
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "time"
  7. _ "embed"
  8. tea "github.com/charmbracelet/bubbletea"
  9. "github.com/langgenius/dify-plugin-daemon/internal/utils/log"
  10. "github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
  11. "github.com/langgenius/dify-plugin-daemon/pkg/entities/constants"
  12. "github.com/langgenius/dify-plugin-daemon/pkg/entities/manifest_entities"
  13. "github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
  14. )
  15. //go:embed templates/python/icon.svg
  16. var icon []byte
  17. func InitPlugin() {
  18. m := initialize()
  19. p := tea.NewProgram(m)
  20. if result, err := p.Run(); err != nil {
  21. fmt.Println("Error running program:", err)
  22. } else {
  23. if m, ok := result.(model); ok {
  24. if m.completed {
  25. m.createPlugin()
  26. }
  27. } else {
  28. log.Error("Error running program:", err)
  29. return
  30. }
  31. }
  32. }
  33. type subMenuKey string
  34. const (
  35. SUB_MENU_KEY_PROFILE subMenuKey = "profile"
  36. SUB_MENU_KEY_LANGUAGE subMenuKey = "language"
  37. SUB_MENU_KEY_CATEGORY subMenuKey = "category"
  38. SUB_MENU_KEY_PERMISSION subMenuKey = "permission"
  39. )
  40. type model struct {
  41. subMenus map[subMenuKey]subMenu
  42. subMenuSeq []subMenuKey
  43. currentSubMenu subMenuKey
  44. completed bool
  45. }
  46. func initialize() model {
  47. m := model{}
  48. m.subMenus = map[subMenuKey]subMenu{
  49. SUB_MENU_KEY_PROFILE: newProfile(),
  50. SUB_MENU_KEY_LANGUAGE: newLanguage(),
  51. SUB_MENU_KEY_CATEGORY: newCategory(),
  52. SUB_MENU_KEY_PERMISSION: newPermission(plugin_entities.PluginPermissionRequirement{}),
  53. }
  54. m.currentSubMenu = SUB_MENU_KEY_PROFILE
  55. m.subMenuSeq = []subMenuKey{
  56. SUB_MENU_KEY_PROFILE,
  57. SUB_MENU_KEY_LANGUAGE,
  58. SUB_MENU_KEY_CATEGORY,
  59. SUB_MENU_KEY_PERMISSION,
  60. }
  61. return m
  62. }
  63. func (m model) Init() tea.Cmd {
  64. return nil
  65. }
  66. func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  67. currentSubMenu, event, cmd := m.subMenus[m.currentSubMenu].Update(msg)
  68. m.subMenus[m.currentSubMenu] = currentSubMenu
  69. switch event {
  70. case SUB_MENU_EVENT_NEXT:
  71. if m.currentSubMenu != m.subMenuSeq[len(m.subMenuSeq)-1] {
  72. // move the current sub menu to the next one
  73. for i, key := range m.subMenuSeq {
  74. if key == m.currentSubMenu {
  75. // check if the next sub menu is permission
  76. if key == SUB_MENU_KEY_CATEGORY {
  77. // get the type of current category
  78. category := m.subMenus[SUB_MENU_KEY_CATEGORY].(category).Category()
  79. if category == "agent-strategy" {
  80. // update the permission to add tool and model invocation
  81. perm := m.subMenus[SUB_MENU_KEY_PERMISSION].(permission)
  82. perm.UpdatePermission(plugin_entities.PluginPermissionRequirement{
  83. Tool: &plugin_entities.PluginPermissionToolRequirement{
  84. Enabled: true,
  85. },
  86. Model: &plugin_entities.PluginPermissionModelRequirement{
  87. Enabled: true,
  88. LLM: true,
  89. },
  90. })
  91. m.subMenus[SUB_MENU_KEY_PERMISSION] = perm
  92. }
  93. }
  94. m.currentSubMenu = m.subMenuSeq[i+1]
  95. break
  96. }
  97. }
  98. } else {
  99. m.completed = true
  100. return m, tea.Quit
  101. }
  102. case SUB_MENU_EVENT_PREV:
  103. if m.currentSubMenu != m.subMenuSeq[0] {
  104. // move the current sub menu to the previous one
  105. for i, key := range m.subMenuSeq {
  106. if key == m.currentSubMenu {
  107. m.currentSubMenu = m.subMenuSeq[i-1]
  108. break
  109. }
  110. }
  111. }
  112. }
  113. return m, cmd
  114. }
  115. func (m model) View() string {
  116. return m.subMenus[m.currentSubMenu].View()
  117. }
  118. func (m model) createPlugin() {
  119. permission := m.subMenus[SUB_MENU_KEY_PERMISSION].(permission).Permission()
  120. manifest := &plugin_entities.PluginDeclaration{
  121. PluginDeclarationWithoutAdvancedFields: plugin_entities.PluginDeclarationWithoutAdvancedFields{
  122. Version: manifest_entities.Version("0.0.1"),
  123. Type: manifest_entities.PluginType,
  124. Icon: "icon.svg",
  125. Author: m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Author(),
  126. Name: m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
  127. Description: plugin_entities.NewI18nObject(m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Description()),
  128. CreatedAt: time.Now(),
  129. Resource: plugin_entities.PluginResourceRequirement{
  130. Memory: 1024 * 1024 * 256, // 256MB
  131. Permission: &permission,
  132. },
  133. Label: plugin_entities.NewI18nObject(m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name()),
  134. Privacy: parser.ToPtr("PRIVACY.md"),
  135. },
  136. }
  137. categoryString := m.subMenus[SUB_MENU_KEY_CATEGORY].(category).Category()
  138. if categoryString == "tool" {
  139. manifest.Plugins.Tools = []string{fmt.Sprintf("provider/%s.yaml", manifest.Name)}
  140. }
  141. if categoryString == "llm" ||
  142. categoryString == "text-embedding" ||
  143. categoryString == "speech2text" ||
  144. categoryString == "moderation" ||
  145. categoryString == "rerank" ||
  146. categoryString == "tts" {
  147. manifest.Plugins.Models = []string{fmt.Sprintf("provider/%s.yaml", manifest.Name)}
  148. }
  149. if categoryString == "extension" {
  150. manifest.Plugins.Endpoints = []string{fmt.Sprintf("group/%s.yaml", manifest.Name)}
  151. }
  152. if categoryString == "agent-strategy" {
  153. manifest.Plugins.AgentStrategies = []string{fmt.Sprintf("provider/%s.yaml", manifest.Name)}
  154. }
  155. manifest.Meta = plugin_entities.PluginMeta{
  156. Version: "0.0.1",
  157. Arch: []constants.Arch{
  158. constants.AMD64,
  159. constants.ARM64,
  160. },
  161. Runner: plugin_entities.PluginRunner{},
  162. }
  163. switch m.subMenus[SUB_MENU_KEY_LANGUAGE].(language).Language() {
  164. case constants.Python:
  165. manifest.Meta.Runner.Entrypoint = "main"
  166. manifest.Meta.Runner.Language = constants.Python
  167. manifest.Meta.Runner.Version = "3.12"
  168. default:
  169. log.Error("unsupported language: %s", m.subMenus[SUB_MENU_KEY_LANGUAGE].(language).Language())
  170. return
  171. }
  172. success := false
  173. clear := func() {
  174. if !success {
  175. os.RemoveAll(manifest.Name)
  176. }
  177. }
  178. defer clear()
  179. manifestFile := marshalYamlBytes(manifest)
  180. // create the plugin directory
  181. cwd, err := os.Getwd()
  182. if err != nil {
  183. log.Error("failed to get current working directory: %s", err)
  184. return
  185. }
  186. pluginDir := filepath.Join(cwd, manifest.Name)
  187. if err := writeFile(filepath.Join(pluginDir, "manifest.yaml"), string(manifestFile)); err != nil {
  188. log.Error("failed to write manifest file: %s", err)
  189. return
  190. }
  191. // create icon.svg
  192. if err := writeFile(filepath.Join(pluginDir, "_assets", "icon.svg"), string(icon)); err != nil {
  193. log.Error("failed to write icon file: %s", err)
  194. return
  195. }
  196. // create README.md
  197. readme, err := renderTemplate(README, manifest, []string{})
  198. if err != nil {
  199. log.Error("failed to render README template: %s", err)
  200. return
  201. }
  202. if err := writeFile(filepath.Join(pluginDir, "README.md"), readme); err != nil {
  203. log.Error("failed to write README file: %s", err)
  204. return
  205. }
  206. // create .env.example
  207. if err := writeFile(filepath.Join(pluginDir, ".env.example"), string(ENV_EXAMPLE)); err != nil {
  208. log.Error("failed to write .env.example file: %s", err)
  209. return
  210. }
  211. // create PRIVACY.md
  212. if err := writeFile(filepath.Join(pluginDir, "PRIVACY.md"), string(PRIVACY)); err != nil {
  213. log.Error("failed to write PRIVACY file: %s", err)
  214. return
  215. }
  216. err = createPythonEnvironment(
  217. pluginDir,
  218. manifest.Meta.Runner.Entrypoint,
  219. manifest,
  220. m.subMenus[SUB_MENU_KEY_CATEGORY].(category).Category(),
  221. )
  222. if err != nil {
  223. log.Error("failed to create python environment: %s", err)
  224. return
  225. }
  226. success = true
  227. log.Info("plugin %s created successfully, you can refer to `%s/GUIDE.md` for more information about how to develop it", manifest.Name, manifest.Name)
  228. }