permission.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. package plugin
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strconv"
  7. "strings"
  8. ti "github.com/charmbracelet/bubbles/textinput"
  9. tea "github.com/charmbracelet/bubbletea"
  10. "github.com/langgenius/dify-plugin-daemon/internal/core/plugin_packager/decoder"
  11. "github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
  12. "github.com/langgenius/dify-plugin-daemon/internal/utils/log"
  13. )
  14. var permissionKeySeq = []string{
  15. "tool.enabled",
  16. "model.enabled",
  17. "model.llm",
  18. "model.text_embedding",
  19. "model.rerank",
  20. "model.tts",
  21. "model.speech2text",
  22. "model.moderation",
  23. "app.enabled",
  24. "storage.enabled",
  25. "storage.size",
  26. "endpoint.enabled",
  27. }
  28. type permission struct {
  29. cursor string
  30. permission plugin_entities.PluginPermissionRequirement
  31. storageSizeEditor ti.Model
  32. }
  33. func newPermission() permission {
  34. return permission{
  35. cursor: permissionKeySeq[0],
  36. storageSizeEditor: ti.New(),
  37. }
  38. }
  39. func (p permission) Permission() plugin_entities.PluginPermissionRequirement {
  40. return p.permission
  41. }
  42. func (p permission) View() string {
  43. cursor := func(key string) string {
  44. if p.cursor == key {
  45. return "→ "
  46. }
  47. return " "
  48. }
  49. checked := func(enabled bool) string {
  50. if enabled {
  51. return fmt.Sprintf("\033[32m%s\033[0m", "[✔]")
  52. }
  53. return fmt.Sprintf("\033[31m%s\033[0m", "[✘]")
  54. }
  55. s := "Configure the permissions of the plugin, use \033[32mup\033[0m and \033[32mdown\033[0m to navigate, \033[32mtab\033[0m to select, after selection, press \033[32menter\033[0m to finish\n"
  56. s += "Backwards Invocation:\n"
  57. s += "Tools:\n"
  58. s += fmt.Sprintf(" %sEnabled: %v %s You can invoke tools inside Dify if it's enabled %s\n", cursor("tool.enabled"), checked(p.permission.AllowInvokeTool()), YELLOW, RESET)
  59. s += "Models:\n"
  60. s += fmt.Sprintf(" %sEnabled: %v %s You can invoke models inside Dify if it's enabled %s\n", cursor("model.enabled"), checked(p.permission.AllowInvokeModel()), YELLOW, RESET)
  61. s += fmt.Sprintf(" %sLLM: %v %s You can invoke LLM models inside Dify if it's enabled %s\n", cursor("model.llm"), checked(p.permission.AllowInvokeLLM()), YELLOW, RESET)
  62. s += fmt.Sprintf(" %sText Embedding: %v %s You can invoke text embedding models inside Dify if it's enabled %s\n", cursor("model.text_embedding"), checked(p.permission.AllowInvokeTextEmbedding()), YELLOW, RESET)
  63. s += fmt.Sprintf(" %sRerank: %v %s You can invoke rerank models inside Dify if it's enabled %s\n", cursor("model.rerank"), checked(p.permission.AllowInvokeRerank()), YELLOW, RESET)
  64. s += fmt.Sprintf(" %sTTS: %v %s You can invoke TTS models inside Dify if it's enabled %s\n", cursor("model.tts"), checked(p.permission.AllowInvokeTTS()), YELLOW, RESET)
  65. s += fmt.Sprintf(" %sSpeech2Text: %v %s You can invoke speech2text models inside Dify if it's enabled %s\n", cursor("model.speech2text"), checked(p.permission.AllowInvokeSpeech2Text()), YELLOW, RESET)
  66. s += fmt.Sprintf(" %sModeration: %v %s You can invoke moderation models inside Dify if it's enabled %s\n", cursor("model.moderation"), checked(p.permission.AllowInvokeModeration()), YELLOW, RESET)
  67. s += "Apps:\n"
  68. s += fmt.Sprintf(" %sEnabled: %v %s Ability to invoke apps like BasicChat/ChatFlow/Agent/Workflow etc. %s\n", cursor("app.enabled"), checked(p.permission.AllowInvokeApp()), YELLOW, RESET)
  69. s += "Resources:\n"
  70. s += "Storage:\n"
  71. s += fmt.Sprintf(" %sEnabled: %v %s Persistence storage for the plugin %s\n", cursor("storage.enabled"), checked(p.permission.AllowInvokeStorage()), YELLOW, RESET)
  72. if p.permission.AllowInvokeStorage() {
  73. s += fmt.Sprintf(" %sSize: %v\n", cursor("storage.size"), p.storageSizeEditor.View())
  74. } else {
  75. s += fmt.Sprintf(" %sSize: %v %s The maximum size of the storage %s\n", cursor("storage.size"), "N/A", YELLOW, RESET)
  76. }
  77. s += "Endpoints:\n"
  78. s += fmt.Sprintf(" %sEnabled: %v %s Ability to register endpoints %s\n", cursor("endpoint.enabled"), checked(p.permission.AllowRegisterEndpoint()), YELLOW, RESET)
  79. return s
  80. }
  81. func (p *permission) edit() {
  82. if p.cursor == "tool.enabled" {
  83. if p.permission.AllowInvokeTool() {
  84. p.permission.Tool = nil
  85. } else {
  86. p.permission.Tool = &plugin_entities.PluginPermissionToolRequirement{
  87. Enabled: true,
  88. }
  89. }
  90. }
  91. if strings.HasPrefix(p.cursor, "model.") {
  92. if p.permission.AllowInvokeModel() {
  93. if p.cursor == "model.enabled" {
  94. p.permission.Model = nil
  95. return
  96. }
  97. } else {
  98. p.permission.Model = &plugin_entities.PluginPermissionModelRequirement{
  99. Enabled: true,
  100. }
  101. }
  102. }
  103. if p.cursor == "model.llm" {
  104. if p.permission.AllowInvokeLLM() {
  105. p.permission.Model.LLM = false
  106. } else {
  107. p.permission.Model.LLM = true
  108. }
  109. }
  110. if p.cursor == "model.text_embedding" {
  111. if p.permission.AllowInvokeTextEmbedding() {
  112. p.permission.Model.TextEmbedding = false
  113. } else {
  114. p.permission.Model.TextEmbedding = true
  115. }
  116. }
  117. if p.cursor == "model.rerank" {
  118. if p.permission.AllowInvokeRerank() {
  119. p.permission.Model.Rerank = false
  120. } else {
  121. p.permission.Model.Rerank = true
  122. }
  123. }
  124. if p.cursor == "model.tts" {
  125. if p.permission.AllowInvokeTTS() {
  126. p.permission.Model.TTS = false
  127. } else {
  128. p.permission.Model.TTS = true
  129. }
  130. }
  131. if p.cursor == "model.speech2text" {
  132. if p.permission.AllowInvokeSpeech2Text() {
  133. p.permission.Model.Speech2text = false
  134. } else {
  135. p.permission.Model.Speech2text = true
  136. }
  137. }
  138. if p.cursor == "model.moderation" {
  139. if p.permission.AllowInvokeModeration() {
  140. p.permission.Model.Moderation = false
  141. } else {
  142. p.permission.Model.Moderation = true
  143. }
  144. }
  145. if p.cursor == "app.enabled" {
  146. if p.permission.AllowInvokeApp() {
  147. p.permission.App = nil
  148. } else {
  149. p.permission.App = &plugin_entities.PluginPermissionAppRequirement{
  150. Enabled: true,
  151. }
  152. }
  153. }
  154. if p.cursor == "storage.enabled" {
  155. if p.permission.AllowInvokeStorage() {
  156. p.permission.Storage = nil
  157. } else {
  158. p.permission.Storage = &plugin_entities.PluginPermissionStorageRequirement{
  159. Enabled: true,
  160. Size: 1048576,
  161. }
  162. p.storageSizeEditor.SetValue(fmt.Sprintf("%d", p.permission.Storage.Size))
  163. }
  164. }
  165. if p.cursor == "endpoint.enabled" {
  166. if p.permission.AllowRegisterEndpoint() {
  167. p.permission.Endpoint = nil
  168. } else {
  169. p.permission.Endpoint = &plugin_entities.PluginPermissionEndpointRequirement{
  170. Enabled: true,
  171. }
  172. }
  173. }
  174. }
  175. func (p *permission) updateStorageSize() {
  176. if p.cursor == "storage.size" {
  177. // set the storage size editor to the current storage size
  178. if p.permission.AllowInvokeStorage() {
  179. p.storageSizeEditor.SetValue(fmt.Sprintf("%d", p.permission.Storage.Size))
  180. p.storageSizeEditor.Focus()
  181. }
  182. } else {
  183. p.storageSizeEditor.Blur()
  184. // get the storage size from the editor
  185. if p.permission.AllowInvokeStorage() {
  186. p.permission.Storage.Size, _ = strconv.ParseUint(p.storageSizeEditor.Value(), 10, 64)
  187. }
  188. }
  189. }
  190. func (p permission) Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd) {
  191. switch msg := msg.(type) {
  192. case tea.KeyMsg:
  193. switch msg.String() {
  194. case "ctrl+c":
  195. return p, SUB_MENU_EVENT_NONE, tea.Quit
  196. case "down":
  197. // find the next key in the permissionKeySeq
  198. for i, key := range permissionKeySeq {
  199. if key == p.cursor {
  200. if i == len(permissionKeySeq)-1 {
  201. p.cursor = permissionKeySeq[0]
  202. } else {
  203. p.cursor = permissionKeySeq[i+1]
  204. }
  205. p.updateStorageSize()
  206. break
  207. }
  208. }
  209. case "up":
  210. // find the previous key in the permissionKeySeq
  211. for i, key := range permissionKeySeq {
  212. if key == p.cursor {
  213. if i == 0 {
  214. p.cursor = permissionKeySeq[len(permissionKeySeq)-1]
  215. } else {
  216. p.cursor = permissionKeySeq[i-1]
  217. }
  218. p.updateStorageSize()
  219. break
  220. }
  221. }
  222. case "tab":
  223. p.edit()
  224. case "enter":
  225. if p.cursor == "storage.size" {
  226. break
  227. }
  228. if p.cursor == "endpoint.enabled" {
  229. p.cursor = permissionKeySeq[0]
  230. p.updateStorageSize()
  231. return p, SUB_MENU_EVENT_NEXT, nil
  232. } else {
  233. // find the next key in the permissionKeySeq
  234. for i, key := range permissionKeySeq {
  235. if key == p.cursor {
  236. if i == len(permissionKeySeq)-1 {
  237. p.cursor = permissionKeySeq[0]
  238. } else {
  239. p.cursor = permissionKeySeq[i+1]
  240. }
  241. p.updateStorageSize()
  242. break
  243. }
  244. }
  245. }
  246. }
  247. }
  248. // update storage size editor
  249. if p.cursor == "storage.size" {
  250. if p.storageSizeEditor.Focused() {
  251. // check if msg is a number
  252. model, cmd := p.storageSizeEditor.Update(msg)
  253. p.storageSizeEditor = model
  254. return p, SUB_MENU_EVENT_NONE, cmd
  255. }
  256. }
  257. return p, SUB_MENU_EVENT_NONE, nil
  258. }
  259. func (p permission) Init() tea.Cmd {
  260. return nil
  261. }
  262. // TODO: optimize implementation
  263. type permissionModel struct {
  264. permission
  265. }
  266. func (p permissionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  267. m, subMenuEvent, cmd := p.permission.Update(msg)
  268. p.permission = m.(permission)
  269. if subMenuEvent == SUB_MENU_EVENT_NEXT {
  270. return p, tea.Quit
  271. }
  272. return p, cmd
  273. }
  274. func (p permissionModel) View() string {
  275. return p.permission.View()
  276. }
  277. func EditPermission(pluginPath string) {
  278. plugin, err := decoder.NewFSPluginDecoder(pluginPath)
  279. if err != nil {
  280. log.Error("decode plugin failed, error: %v", err)
  281. os.Exit(1)
  282. return
  283. }
  284. manifest, err := plugin.Manifest()
  285. if err != nil {
  286. log.Error("get manifest failed, error: %v", err)
  287. os.Exit(1)
  288. return
  289. }
  290. // create a new permission
  291. m := permissionModel{
  292. permission: newPermission(),
  293. }
  294. m.permission.permission = *manifest.Resource.Permission
  295. p := tea.NewProgram(m)
  296. if result, err := p.Run(); err != nil {
  297. fmt.Println("Error running program:", err)
  298. } else {
  299. if m, ok := result.(permissionModel); ok {
  300. // save the manifest
  301. manifestPath := filepath.Join(pluginPath, "manifest.yaml")
  302. manifest.Resource.Permission = &m.permission.permission
  303. if err := writeFile(
  304. manifestPath,
  305. string(marshalYamlBytes(manifest.PluginDeclarationWithoutAdvancedFields)),
  306. ); err != nil {
  307. log.Error("write manifest failed, error: %v", err)
  308. os.Exit(1)
  309. return
  310. }
  311. } else {
  312. log.Error("Error running program:", err)
  313. return
  314. }
  315. }
  316. }