Browse Source

feat: text encoder plugin api

Junyi Du 2 years ago
parent
commit
470f19ef15

+ 8 - 0
libs/src/LSPlugin.ts

@@ -327,6 +327,13 @@ export interface IPluginSearchServiceHooks {
   onGraphRemoved: (graph: string, opts?: {}) => Promise<any>
 }
 
+export interface IPluginTextEncoderServiceHooks {
+  name: string
+  options?: Record<string, any>
+
+  textEncode: (text: string) => Promise<string>
+}
+
 /**
  * App level APIs
  */
@@ -342,6 +349,7 @@ export interface IAppProxy {
 
   // services
   registerSearchService<T extends IPluginSearchServiceHooks>(s: T): void
+  registerTextEncoderService<T extends IPluginTextEncoderServiceHooks>(s: T): void
 
   // commands
   registerCommand: (

+ 62 - 0
libs/src/modules/LSPlugin.TextEncoder.ts

@@ -0,0 +1,62 @@
+import { IPluginTextEncoderServiceHooks } from '../LSPlugin'
+import { LSPluginUser } from '../LSPlugin.user'
+import { isFunction } from 'lodash-es'
+
+export class LSPluginTextEncoderService {
+
+  /**
+   * @param ctx
+   * @param serviceHooks
+   */
+  constructor(
+    private ctx: LSPluginUser,
+    private serviceHooks: IPluginTextEncoderServiceHooks
+  ) {
+    ctx._execCallableAPI(
+      'register-TextEncoder-service',
+      ctx.baseInfo.id,
+      serviceHooks.name,
+      serviceHooks.options
+    )
+
+    // hook events TODO: remove listeners
+    const wrapHookEvent = (k) => `service:textEncoder:${k}:${serviceHooks.name}`
+
+    Object.entries(
+      {
+        textEncode: {f: 'textEncode', args: ['text'], reply: true}
+      }
+    ).forEach(
+      ([k, v]) => {
+        const hookEvent = wrapHookEvent(k)
+        ctx.caller.on(hookEvent, async (payload: any) => {
+          if (isFunction(serviceHooks?.[v.f])) {
+            let ret = null
+
+            try {
+              ret = await serviceHooks[v.f].apply(
+                serviceHooks, (v.args || []).map((prop: any) => {
+                  if (!payload) return
+                  if (prop === true) return payload
+                  if (payload.hasOwnProperty(prop)) {
+                    const ret = payload[prop]
+                    delete payload[prop]
+                    return ret
+                  }
+                })
+              )
+            } catch (e) {
+              console.error('[TextEncoderService] ', e)
+              ret = e
+            } finally {
+              if (v.reply) {
+                ctx.caller.call(
+                  `${hookEvent}:reply`, ret
+                )
+              }
+            }
+          }
+        })
+      })
+  }
+}

+ 40 - 0
src/main/frontend/ai/text_encoder.cljs

@@ -0,0 +1,40 @@
+(ns frontend.ai.text-encoder
+  "Plugin service for text encoder (AI)"
+  (:require [promesa.core :as p]
+            [frontend.state :as state]
+            [frontend.handler.plugin :as plugin-handler]
+            [cljs-bean.core :as bean]))
+
+(defn- call-text-encoder-service!
+  "Handling communication with text encoder plugin
+  When reply? is true, it will listen to the `service:<event>:<name>:reply `event
+  and return a promise of the result"
+  ([service event payload] (call-text-encoder-service! service event payload false))
+  ([service event payload reply?]
+   (when-let [^js pl (plugin-handler/get-plugin-inst (:pid service))]
+     (let [{:keys [name]} service
+           hookEvent (str "service:" event ":" name)]
+       (.call (.-caller pl) hookEvent (bean/->js (merge {:graph (state/get-current-repo)} payload)))
+       (when reply?
+         (-> (p/create (fn [resolve _rej]
+                     (.once (.-caller pl) (str hookEvent ":reply")
+                            (fn [^js e]
+                              (resolve (bean/->clj e))))))
+             (p/timeout 20000)
+             (p/catch #(prn "Timeout waiting reply from text encoder service" hookEvent %))))))))
+
+(defn- text-encode'
+  [text service]
+  (call-text-encoder-service! service "textEncoder:textEncode" {:text text} true))
+
+(defn text-encode
+  "Return a promise of the encoded text"
+  ([text]
+   (text-encode' text (when state/lsp-enabled?
+                        (->> (state/get-all-plugin-services-with-type :text-encoder)
+                             (first)))))
+  ([text encoder-name]
+   (text-encode' text (when state/lsp-enabled?
+                        (->> (state/get-all-plugin-services-with-type :text-encoder)
+                             (filter #(= (:name %) encoder-name))
+                             (first))))))

+ 10 - 0
src/main/frontend/handler/plugin.cljs

@@ -357,6 +357,16 @@
   (when-let [pid (keyword pid)]
     (state/uninstall-plugin-service pid :search)))
 
+(defn register-plugin-text-encoder
+  [pid name opts]
+  (when-let [pid (and name (keyword pid))]
+    (state/install-plugin-service pid :text-encoder name opts)))
+
+(defn unregister-plugin-text-encoders
+  [pid]
+  (when-let [pid (keyword pid)]
+    (state/uninstall-plugin-service pid :text-encoder)))
+
 (defn unregister-plugin-themes
   ([pid] (unregister-plugin-themes pid true))
   ([pid effect]

+ 1 - 0
src/main/frontend/search/plugin.cljs

@@ -6,6 +6,7 @@
             [cljs-bean.core :as bean]))
 
 (defn call-service!
+  "Handling communication with search service plugin"
   ([service event payload] (call-service! service event payload false))
   ([service event payload reply?]
    (when-let [^js pl (plugin-handler/get-plugin-inst (:pid service))]

+ 3 - 1
src/main/frontend/search/protocol.cljs

@@ -1,4 +1,6 @@
-(ns ^:no-doc frontend.search.protocol)
+(ns frontend.search.protocol
+  "Search engine protocols. Interface for unifying different search result sources
+   Like: app built-in search, search engine provided by plugins, etc.")
 
 (defprotocol Engine
   (query [this q option]) 

+ 20 - 1
src/main/frontend/state.cljs

@@ -55,6 +55,8 @@
      :search/graph-filters                  []
      :search/engines                        {}
 
+     :ai/text-encoders                      {}
+
      ;; modals
      :modal/dropdowns                       {}
      :modal/id                              nil
@@ -1542,7 +1544,11 @@ Similar to re-frame subscriptions"
 
          ;; search engines state for results
          (when (= type :search)
-           (set-state! [:search/engines (str pid name)] service)))))))
+           (set-state! [:search/engines (str pid name)] service))
+         
+         ;; text encoders for calling
+         (when (= type :text-encoder)
+           (set-state! [:ai/text-encoders (str pid name)] service)))))))
 
 (defn uninstall-plugin-service
   [pid type-or-all]
@@ -1575,11 +1581,24 @@ Similar to re-frame subscriptions"
                                 (f %) %)))))
 
 (defn reset-plugin-search-engines
+  "Clears all search engine results.
+   Search engine results are stored in the :result of state under :search/engines.
+   Then subscribed by the search modal to display results."
   []
   (when-let [engines (get-all-plugin-search-engines)]
     (set-state! :search/engines
                 (update-vals engines #(assoc % :result nil)))))
 
+(defn get-all-plugin-text-encoders
+  []
+  (:ai/text-encoders @state))
+
+(defn reset-plugin-text-encoder
+  []
+  (when-let [encoders (get-all-plugin-text-encoders)]
+    (set-state! :ai/text-encoders
+                (update-vals encoders #(assoc % :result nil)))))
+
 (defn install-plugin-hook
   ([pid hook] (install-plugin-hook pid hook true))
   ([pid hook opts]

+ 13 - 0
src/main/logseq/api.cljs

@@ -19,6 +19,7 @@
             [frontend.db.utils :as db-utils]
             [frontend.db.query-react :as query-react]
             [frontend.fs :as fs]
+            [frontend.ai.text-encoder :as text-encoder]
             [frontend.handler.dnd :as editor-dnd-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor.property :as editor-property]
@@ -395,6 +396,18 @@
   [pid]
   (plugin-handler/unregister-plugin-search-services pid))
 
+(defn ^:export register_text_encoder
+  [pid name ^js opts]
+  (plugin-handler/register-plugin-text-encoder pid name (bean/->clj opts)))
+
+(defn ^:export unregister_text_encoders
+  [pid]
+  (plugin-handler/unregister-plugin-text-encoders pid))
+
+(defn ^:export text_encode
+  [text encoder-name]
+  (text-encoder/text-encode text encoder-name))
+
 (def ^:export register_plugin_ui_item
   (fn [pid type ^js opts]
     (when-let [opts (bean/->clj opts)]