permission.go 10 KB

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