mirror of https://github.com/Wox-launcher/Wox
feat(mru): Enhance nodejs/python plugin API with MRU restore functionality and dynamic setting improvements
- Updated API interface to support dynamic settings returning PluginSettingDefinitionItem. - Implemented MRU restore callback in both Node.js and Python plugin hosts. - Added MRUData model for handling most recently used data. - Enhanced setting models to include new types and helper functions for creating settings. - Updated package dependencies for Node.js and Python plugins to the latest versions.
This commit is contained in:
parent
4b2018934d
commit
62fdd3b7c7
|
@ -10,6 +10,7 @@ import (
|
|||
"wox/common"
|
||||
"wox/i18n"
|
||||
"wox/setting"
|
||||
"wox/setting/definition"
|
||||
"wox/util"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
@ -34,7 +35,7 @@ type API interface {
|
|||
GetSetting(ctx context.Context, key string) string
|
||||
SaveSetting(ctx context.Context, key string, value string, isPlatformSpecific bool)
|
||||
OnSettingChanged(ctx context.Context, callback func(key string, value string))
|
||||
OnGetDynamicSetting(ctx context.Context, callback func(key string) string)
|
||||
OnGetDynamicSetting(ctx context.Context, callback func(key string) definition.PluginSettingDefinitionItem)
|
||||
OnDeepLink(ctx context.Context, callback func(arguments map[string]string))
|
||||
OnUnload(ctx context.Context, callback func())
|
||||
OnMRURestore(ctx context.Context, callback func(mruData MRUData) (*QueryResult, error))
|
||||
|
@ -142,7 +143,7 @@ func (a *APIImpl) OnSettingChanged(ctx context.Context, callback func(key string
|
|||
a.pluginInstance.SettingChangeCallbacks = append(a.pluginInstance.SettingChangeCallbacks, callback)
|
||||
}
|
||||
|
||||
func (a *APIImpl) OnGetDynamicSetting(ctx context.Context, callback func(key string) string) {
|
||||
func (a *APIImpl) OnGetDynamicSetting(ctx context.Context, callback func(key string) definition.PluginSettingDefinitionItem) {
|
||||
a.pluginInstance.DynamicSettingCallbacks = append(a.pluginInstance.DynamicSettingCallbacks, callback)
|
||||
}
|
||||
|
||||
|
|
|
@ -344,24 +344,19 @@ func (w *WebsocketHost) handleRequestFromPlugin(ctx context.Context, request Jso
|
|||
}
|
||||
|
||||
metadata := pluginInstance.Metadata
|
||||
pluginInstance.API.OnGetDynamicSetting(ctx, func(key string) string {
|
||||
pluginInstance.API.OnGetDynamicSetting(ctx, func(key string) definition.PluginSettingDefinitionItem {
|
||||
result, err := w.invokeMethod(ctx, metadata, "onGetDynamicSetting", map[string]string{
|
||||
"CallbackId": callbackId,
|
||||
"Key": key,
|
||||
})
|
||||
if err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("[%s] failed to get dynamic setting: %s", request.PluginName, err))
|
||||
settingJson, marshalErr := json.Marshal(definition.PluginSettingDefinitionItem{
|
||||
return definition.PluginSettingDefinitionItem{
|
||||
Type: definition.PluginSettingDefinitionTypeLabel,
|
||||
Value: &definition.PluginSettingValueLabel{
|
||||
Content: fmt.Sprintf("failed to get dynamic setting: %s", err),
|
||||
},
|
||||
})
|
||||
if marshalErr != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("[%s] failed to marshal dynamic setting: %s", request.PluginName, marshalErr))
|
||||
return ""
|
||||
}
|
||||
return string(settingJson)
|
||||
}
|
||||
|
||||
// validate the result is a valid definition.PluginSettingDefinitionItem json string
|
||||
|
@ -369,20 +364,15 @@ func (w *WebsocketHost) handleRequestFromPlugin(ctx context.Context, request Jso
|
|||
unmarshalErr := json.Unmarshal([]byte(result.(string)), &setting)
|
||||
if unmarshalErr != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("[%s] failed to unmarshal dynamic setting: %s", request.PluginName, unmarshalErr))
|
||||
settingJson, marshalErr := json.Marshal(definition.PluginSettingDefinitionItem{
|
||||
return definition.PluginSettingDefinitionItem{
|
||||
Type: definition.PluginSettingDefinitionTypeLabel,
|
||||
Value: &definition.PluginSettingValueLabel{
|
||||
Content: fmt.Sprintf("failed to unmarshal dynamic setting: %s", unmarshalErr),
|
||||
},
|
||||
})
|
||||
if marshalErr != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("[%s] failed to marshal dynamic setting: %s", request.PluginName, marshalErr))
|
||||
return ""
|
||||
}
|
||||
return string(settingJson)
|
||||
}
|
||||
|
||||
return result.(string)
|
||||
return setting
|
||||
})
|
||||
w.sendResponseToHost(ctx, request, "")
|
||||
case "OnDeepLink":
|
||||
|
|
|
@ -2,6 +2,7 @@ package plugin
|
|||
|
||||
import (
|
||||
"wox/setting"
|
||||
"wox/setting/definition"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
|
@ -15,7 +16,7 @@ type Instance struct {
|
|||
Host Host // plugin host to run this plugin
|
||||
Setting *setting.PluginSetting // setting for this plugin
|
||||
|
||||
DynamicSettingCallbacks []func(key string) string // dynamic setting callbacks
|
||||
DynamicSettingCallbacks []func(key string) definition.PluginSettingDefinitionItem // dynamic setting callbacks
|
||||
SettingChangeCallbacks []func(key string, value string)
|
||||
DeepLinkCallbacks []func(arguments map[string]string)
|
||||
UnloadCallbacks []func()
|
||||
|
|
|
@ -202,13 +202,7 @@ func convertPluginDto(ctx context.Context, pluginDto dto.PluginDto, pluginInstan
|
|||
if settingDefinition.Type == definition.PluginSettingDefinitionTypeDynamic {
|
||||
replaced := false
|
||||
for _, callback := range pluginInstance.DynamicSettingCallbacks {
|
||||
newSettingDefinitionJson := callback(settingDefinition.Value.GetKey())
|
||||
var newSettingDefinition definition.PluginSettingDefinitionItem
|
||||
unmarshalErr := json.Unmarshal([]byte(newSettingDefinitionJson), &newSettingDefinition)
|
||||
if unmarshalErr != nil {
|
||||
logger.Error(ctx, fmt.Sprintf("failed to unmarshal dynamic setting: %s", unmarshalErr.Error()))
|
||||
continue
|
||||
}
|
||||
newSettingDefinition := callback(settingDefinition.Value.GetKey())
|
||||
if newSettingDefinition.Value != nil && newSettingDefinition.Type != definition.PluginSettingDefinitionTypeDynamic {
|
||||
logger.Debug(ctx, fmt.Sprintf("dynamic setting replaced: %s(%s) -> %s(%s)", settingDefinition.Value.GetKey(), settingDefinition.Type, newSettingDefinition.Value.GetKey(), newSettingDefinition.Type))
|
||||
pluginDto.SettingDefinitions[i] = newSettingDefinition
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wox-launcher/wox-plugin": "^0.0.82",
|
||||
"@wox-launcher/wox-plugin": "^0.0.85",
|
||||
"dayjs": "^1.11.13",
|
||||
"promise-deferred": "^2.0.4",
|
||||
"winston": "^3.17.0",
|
||||
|
|
|
@ -6,8 +6,8 @@ settings:
|
|||
|
||||
dependencies:
|
||||
'@wox-launcher/wox-plugin':
|
||||
specifier: ^0.0.82
|
||||
version: 0.0.82
|
||||
specifier: ^0.0.85
|
||||
version: 0.0.85
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
|
@ -1561,8 +1561,8 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/@wox-launcher/wox-plugin@0.0.82:
|
||||
resolution: {integrity: sha512-BV2I/I7Bu2hperlqdJ2aqZd1j3ZZJk7bsWKChNVvctQ1D6mXoREEexgWYjF8cq11FNMKhMnj9nrRZZjccrD27g==}
|
||||
/@wox-launcher/wox-plugin@0.0.85:
|
||||
resolution: {integrity: sha512-1QbLcd/RvA0oNpfj4CkdpBjiqW8wV5XLwN6Ps1VJGZMrKCuR5S8n/cvMYeVM/IlqT6i8sswKcn014uwPa27ADA==}
|
||||
dev: false
|
||||
|
||||
/JSONStream@1.3.5:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { logger } from "./logger"
|
||||
import path from "path"
|
||||
import { PluginAPI } from "./pluginAPI"
|
||||
import { Context, MapString, Plugin, PluginInitParams, Query, QueryEnv, RefreshableResult, Result, ResultAction, Selection } from "@wox-launcher/wox-plugin"
|
||||
import { Context, MapString, Plugin, PluginInitParams, Query, QueryEnv, RefreshableResult, Result, ResultAction, Selection, MRUData } from "@wox-launcher/wox-plugin"
|
||||
import { WebSocket } from "ws"
|
||||
import * as crypto from "crypto"
|
||||
import { AI } from "@wox-launcher/wox-plugin/types/ai"
|
||||
|
@ -41,6 +41,8 @@ export async function handleRequestFromWox(ctx: Context, request: PluginJsonRpcR
|
|||
return onUnload(ctx, request)
|
||||
case "onLLMStream":
|
||||
return onLLMStream(ctx, request)
|
||||
case "onMRURestore":
|
||||
return onMRURestore(ctx, request)
|
||||
default:
|
||||
logger.info(ctx, `unknown method handler: ${request.Method}`)
|
||||
throw new Error(`unknown method handler: ${request.Method}`)
|
||||
|
@ -107,7 +109,8 @@ async function initPlugin(ctx: Context, request: PluginJsonRpcRequest, ws: WebSo
|
|||
const init = getMethod(ctx, request, "init")
|
||||
const pluginApi = new PluginAPI(ws, request.PluginId, request.PluginName)
|
||||
plugin.API = pluginApi
|
||||
return init(ctx, { API: pluginApi, PluginDirectory: request.Params.PluginDirectory } as PluginInitParams)
|
||||
const initParams: PluginInitParams = { API: pluginApi, PluginDirectory: request.Params.PluginDirectory }
|
||||
return init(ctx, initParams)
|
||||
}
|
||||
|
||||
async function onPluginSettingChange(ctx: Context, request: PluginJsonRpcRequest) {
|
||||
|
@ -298,13 +301,48 @@ async function refresh(ctx: Context, request: PluginJsonRpcRequest) {
|
|||
Tails: refreshedResult.Tails,
|
||||
ContextData: refreshedResult.ContextData,
|
||||
RefreshInterval: refreshedResult.RefreshInterval,
|
||||
Actions: refreshedResult.Actions.map(action => ({
|
||||
Actions: refreshedResult.Actions.map(
|
||||
action =>
|
||||
({
|
||||
Id: action.Id,
|
||||
Name: action.Name,
|
||||
Icon: action.Icon,
|
||||
IsDefault: action.IsDefault,
|
||||
PreventHideAfterAction: action.PreventHideAfterAction,
|
||||
Hotkey: action.Hotkey,
|
||||
} as ResultActionUI))
|
||||
Hotkey: action.Hotkey
|
||||
}) as ResultActionUI
|
||||
)
|
||||
} as RefreshableResultWithResultId
|
||||
}
|
||||
|
||||
async function onMRURestore(ctx: Context, request: PluginJsonRpcRequest): Promise<Result | null> {
|
||||
const pluginInstance = pluginInstances.get(request.PluginId)
|
||||
if (!pluginInstance) {
|
||||
throw new Error(`plugin instance not found: ${request.PluginId}`)
|
||||
}
|
||||
|
||||
const callbackId = request.Params.callbackId
|
||||
const mruDataRaw = JSON.parse(request.Params.mruData)
|
||||
|
||||
// Convert raw data to MRUData type
|
||||
const mruData: MRUData = {
|
||||
PluginID: mruDataRaw.PluginID || "",
|
||||
Title: mruDataRaw.Title || "",
|
||||
SubTitle: mruDataRaw.SubTitle || "",
|
||||
Icon: mruDataRaw.Icon || { ImageType: "absolute", ImageData: "" },
|
||||
ContextData: mruDataRaw.ContextData || ""
|
||||
}
|
||||
|
||||
const callback = pluginInstance.API.mruRestoreCallbacks.get(callbackId)
|
||||
if (!callback) {
|
||||
throw new Error(`MRU restore callback not found: ${callbackId}`)
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await callback(mruData)
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.error(ctx, `MRU restore callback error: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChangeQueryParam, Context, MapString, PublicAPI } from "@wox-launcher/wox-plugin"
|
||||
import { ChangeQueryParam, Context, MapString, PublicAPI, Result } from "@wox-launcher/wox-plugin"
|
||||
import { WebSocket } from "ws"
|
||||
import * as crypto from "crypto"
|
||||
import { waitingForResponse } from "./index"
|
||||
|
@ -6,6 +6,7 @@ import Deferred from "promise-deferred"
|
|||
import { logger } from "./logger"
|
||||
import { MetadataCommand, PluginSettingDefinitionItem } from "@wox-launcher/wox-plugin/types/setting"
|
||||
import { AI } from "@wox-launcher/wox-plugin/types/ai"
|
||||
import { MRUData } from "@wox-launcher/wox-plugin"
|
||||
import { PluginJsonRpcTypeRequest } from "./jsonrpc"
|
||||
import { PluginJsonRpcRequest } from "./types"
|
||||
|
||||
|
@ -18,6 +19,7 @@ export class PluginAPI implements PublicAPI {
|
|||
deepLinkCallbacks: Map<string, (params: MapString) => void>
|
||||
unloadCallbacks: Map<string, () => Promise<void>>
|
||||
llmStreamCallbacks: Map<string, AI.ChatStreamFunc>
|
||||
mruRestoreCallbacks: Map<string, (mruData: MRUData) => Promise<Result | null>>
|
||||
|
||||
constructor(ws: WebSocket, pluginId: string, pluginName: string) {
|
||||
this.ws = ws
|
||||
|
@ -28,6 +30,7 @@ export class PluginAPI implements PublicAPI {
|
|||
this.deepLinkCallbacks = new Map<string, (params: MapString) => void>()
|
||||
this.unloadCallbacks = new Map<string, () => Promise<void>>()
|
||||
this.llmStreamCallbacks = new Map<string, AI.ChatStreamFunc>()
|
||||
this.mruRestoreCallbacks = new Map<string, (mruData: MRUData) => Promise<Result | null>>()
|
||||
}
|
||||
|
||||
async invokeMethod(ctx: Context, method: string, params: { [key: string]: string }): Promise<unknown> {
|
||||
|
@ -124,4 +127,10 @@ export class PluginAPI implements PublicAPI {
|
|||
this.llmStreamCallbacks.set(callbackId, callback)
|
||||
await this.invokeMethod(ctx, "LLMStream", { callbackId, conversations: JSON.stringify(conversations) })
|
||||
}
|
||||
|
||||
async OnMRURestore(ctx: Context, callback: (mruData: MRUData) => Promise<Result | null>): Promise<void> {
|
||||
const callbackId = crypto.randomUUID()
|
||||
this.mruRestoreCallbacks.set(callbackId, callback)
|
||||
await this.invokeMethod(ctx, "OnMRURestore", { callbackId })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ readme = "README.md"
|
|||
requires-python = ">=3.10"
|
||||
license = "GPL-3.0"
|
||||
authors = [{ name = "Wox Team", email = "qianlifeng@gmail.com" }]
|
||||
dependencies = ["loguru", "websockets", "wox-plugin==0.0.48"]
|
||||
dependencies = ["loguru", "websockets", "wox-plugin==0.0.49"]
|
||||
|
||||
[project.scripts]
|
||||
run = "wox_plugin_host.__main__:run"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
import importlib.util
|
||||
import importlib
|
||||
from os import path
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
|
@ -12,6 +12,7 @@ from wox_plugin import (
|
|||
RefreshableResult,
|
||||
PluginInitParams,
|
||||
ActionContext,
|
||||
MRUData,
|
||||
)
|
||||
from .plugin_manager import plugin_instances, PluginInstance
|
||||
from .plugin_api import PluginAPI
|
||||
|
@ -38,6 +39,8 @@ async def handle_request_from_wox(ctx: Context, request: Dict[str, Any], ws: web
|
|||
return await refresh(ctx, request)
|
||||
elif method == "unloadPlugin":
|
||||
return await unload_plugin(ctx, request)
|
||||
elif method == "onMRURestore":
|
||||
return await on_mru_restore(ctx, request)
|
||||
else:
|
||||
await logger.info(ctx.get_trace_id(), f"unknown method handler: {method}")
|
||||
raise Exception(f"unknown method handler: {method}")
|
||||
|
@ -325,3 +328,49 @@ async def unload_plugin(ctx: Context, request: Dict[str, Any]) -> None:
|
|||
f"<{plugin_name}> unload plugin failed: {str(e)}\nStack trace:\n{error_stack}",
|
||||
)
|
||||
raise e
|
||||
|
||||
|
||||
async def on_mru_restore(ctx: Context, request: Dict[str, Any]) -> Any:
|
||||
"""Handle MRU restore callback"""
|
||||
plugin_id = request.get("PluginId")
|
||||
if not plugin_id:
|
||||
raise Exception("PluginId is required")
|
||||
|
||||
params = request.get("Params", {})
|
||||
callback_id = params.get("callbackId")
|
||||
mru_data_dict = json.loads(params.get("mruData", "{}"))
|
||||
|
||||
plugin_instance = plugin_instances.get(plugin_id)
|
||||
if not plugin_instance:
|
||||
raise Exception(f"plugin instance not found: {plugin_id}")
|
||||
|
||||
if not plugin_instance.api:
|
||||
raise Exception(f"plugin API not found: {plugin_id}")
|
||||
|
||||
# Type cast to access implementation-specific attributes
|
||||
from .plugin_api import PluginAPI
|
||||
|
||||
api = plugin_instance.api
|
||||
if not isinstance(api, PluginAPI):
|
||||
raise Exception(f"Invalid API type for plugin: {plugin_id}")
|
||||
|
||||
callback = api.mru_restore_callbacks.get(callback_id)
|
||||
if not callback:
|
||||
raise Exception(f"MRU restore callback not found: {callback_id}")
|
||||
|
||||
try:
|
||||
# Convert dict to MRUData object for type safety
|
||||
mru_data = MRUData.from_dict(mru_data_dict)
|
||||
|
||||
# Call the callback (may or may not be async)
|
||||
result = callback(mru_data)
|
||||
if hasattr(result, "__await__"):
|
||||
result = await result # type: ignore
|
||||
|
||||
# Convert Result object back to dict for JSON serialization
|
||||
if result is not None:
|
||||
return result.__dict__
|
||||
return None
|
||||
except Exception as e:
|
||||
await logger.error(ctx.get_trace_id(), f"MRU restore callback error: {str(e)}")
|
||||
raise e
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from typing import Any, Dict, Callable
|
||||
from typing import Any, Dict, Callable, Optional
|
||||
import websockets
|
||||
from . import logger
|
||||
from wox_plugin import (
|
||||
|
@ -12,6 +12,9 @@ from wox_plugin import (
|
|||
Conversation,
|
||||
AIModel,
|
||||
ChatStreamCallback,
|
||||
MRUData,
|
||||
Result,
|
||||
PluginSettingDefinitionItem,
|
||||
)
|
||||
from .constants import PLUGIN_JSONRPC_TYPE_REQUEST
|
||||
from .plugin_manager import waiting_for_response
|
||||
|
@ -23,10 +26,11 @@ class PluginAPI(PublicAPI):
|
|||
self.plugin_id = plugin_id
|
||||
self.plugin_name = plugin_name
|
||||
self.setting_change_callbacks: Dict[str, Callable[[str, str], None]] = {}
|
||||
self.get_dynamic_setting_callbacks: Dict[str, Callable[[str], str]] = {}
|
||||
self.get_dynamic_setting_callbacks: Dict[str, Callable[[str], PluginSettingDefinitionItem]] = {}
|
||||
self.deep_link_callbacks: Dict[str, Callable[[Dict[str, str]], None]] = {}
|
||||
self.unload_callbacks: Dict[str, Callable[[], None]] = {}
|
||||
self.llm_stream_callbacks: Dict[str, ChatStreamCallback] = {}
|
||||
self.mru_restore_callbacks: Dict[str, Callable[[MRUData], Optional[Result]]] = {}
|
||||
|
||||
async def invoke_method(self, ctx: Context, method: str, params: Dict[str, Any]) -> Any:
|
||||
"""Invoke a method on Wox"""
|
||||
|
@ -110,7 +114,7 @@ class PluginAPI(PublicAPI):
|
|||
self.setting_change_callbacks[callback_id] = callback
|
||||
await self.invoke_method(ctx, "OnSettingChanged", {"callbackId": callback_id})
|
||||
|
||||
async def on_get_dynamic_setting(self, ctx: Context, callback: Callable[[str], str]) -> None:
|
||||
async def on_get_dynamic_setting(self, ctx: Context, callback: Callable[[str], PluginSettingDefinitionItem]) -> None:
|
||||
"""Register dynamic setting callback"""
|
||||
callback_id = str(uuid.uuid4())
|
||||
self.get_dynamic_setting_callbacks[callback_id] = callback
|
||||
|
@ -154,3 +158,9 @@ class PluginAPI(PublicAPI):
|
|||
"conversations": json.dumps([conv.__dict__ for conv in conversations]),
|
||||
},
|
||||
)
|
||||
|
||||
async def on_mru_restore(self, ctx: Context, callback: Callable[[MRUData], Optional[Result]]) -> None:
|
||||
"""Register MRU restore callback"""
|
||||
callback_id = str(uuid.uuid4())
|
||||
self.mru_restore_callbacks[callback_id] = callback
|
||||
await self.invoke_method(ctx, "OnMRURestore", {"callbackId": callback_id})
|
||||
|
|
|
@ -253,11 +253,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "wox-plugin"
|
||||
version = "0.0.48"
|
||||
version = "0.0.49"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/59/e7/1a4f6701f653d7243d8ec35b29aad8e377760e03a61092e3950b53f2aadb/wox_plugin-0.0.48.tar.gz", hash = "sha256:132820a60dddc5d130c049ce302aba519b10e6a409e87453be393472f55153ba", size = 34994 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/66/9961ded8a71981736334b2f95b70340903c233df6bdaf256bd6b2107255f/wox_plugin-0.0.49.tar.gz", hash = "sha256:734437d909d14f2b96509cf4d66e57731bff9fe6344dbfada4a93ce843f8eb44", size = 36975 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/68/0b131ae43f8a95fb2f8eb6f36c69606accb0c2729544ea92082256bd9957/wox_plugin-0.0.48-py3-none-any.whl", hash = "sha256:5a34cd5a59f8dbd218a45618ee00ce0c4c50e03d39f1dc237fcbd0ad34f013d5", size = 10386 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/53/dc3a17c6e9668b903fe5f4ff2d04c949f350cd3e556535c95c056b6d9491/wox_plugin-0.0.49-py3-none-any.whl", hash = "sha256:cd33ea38c5aecc576c2aefbe5427e04400ff13de997b1126f6d7d9e426edb446", size = 13125 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -284,5 +284,5 @@ requires-dist = [
|
|||
{ name = "ruff", marker = "extra == 'dev'" },
|
||||
{ name = "shiv", marker = "extra == 'dev'" },
|
||||
{ name = "websockets" },
|
||||
{ name = "wox-plugin", specifier = "==0.0.48" },
|
||||
{ name = "wox-plugin", specifier = "==0.0.49" },
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@wox-launcher/wox-plugin",
|
||||
"version": "0.0.83",
|
||||
"version": "0.0.85",
|
||||
"description": "All nodejs plugin for Wox should use types in this package",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -152,6 +152,14 @@ export interface ActionContext {
|
|||
ContextData: string
|
||||
}
|
||||
|
||||
export interface MRUData {
|
||||
PluginID: string
|
||||
Title: string
|
||||
SubTitle: string
|
||||
Icon: WoxImage
|
||||
ContextData: string
|
||||
}
|
||||
|
||||
export interface PluginInitParams {
|
||||
API: PublicAPI
|
||||
PluginDirectory: string
|
||||
|
@ -216,7 +224,7 @@ export interface PublicAPI {
|
|||
/**
|
||||
* Get dynamic setting definition
|
||||
*/
|
||||
OnGetDynamicSetting: (ctx: Context, callback: (key: string) => string) => Promise<void>
|
||||
OnGetDynamicSetting: (ctx: Context, callback: (key: string) => PluginSettingDefinitionItem) => Promise<void>
|
||||
|
||||
/**
|
||||
* Register deep link callback
|
||||
|
@ -237,6 +245,14 @@ export interface PublicAPI {
|
|||
* Chat using LLM
|
||||
*/
|
||||
LLMStream: (ctx: Context, conversations: AI.Conversation[], callback: AI.ChatStreamFunc) => Promise<void>
|
||||
|
||||
/**
|
||||
* Register MRU restore callback
|
||||
* @param ctx Context
|
||||
* @param callback Callback function that takes MRUData and returns Result or null
|
||||
* Return null if the MRU data is no longer valid
|
||||
*/
|
||||
OnMRURestore: (ctx: Context, callback: (mruData: MRUData) => Promise<Result | null>) => Promise<void>
|
||||
}
|
||||
|
||||
export type WoxImageType = "absolute" | "relative" | "base64" | "svg" | "url" | "emoji" | "lottie"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "wox-plugin"
|
||||
version = "0.0.48"
|
||||
version = "0.0.49"
|
||||
description = "Python plugin SDK for Wox launcher"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
|
|
@ -35,6 +35,19 @@ from .models.ai import (
|
|||
)
|
||||
from .models.image import WoxImage, WoxImageType
|
||||
from .models.preview import WoxPreview, WoxPreviewType, WoxPreviewScrollPosition
|
||||
from .models.mru import MRUData, MRURestoreCallback
|
||||
from .models.setting import (
|
||||
PluginSettingDefinitionItem,
|
||||
PluginSettingDefinitionType,
|
||||
PluginSettingDefinitionValue,
|
||||
PluginSettingValueStyle,
|
||||
PluginSettingValueTextBox,
|
||||
PluginSettingValueCheckBox,
|
||||
PluginSettingValueLabel,
|
||||
create_textbox_setting,
|
||||
create_checkbox_setting,
|
||||
create_label_setting,
|
||||
)
|
||||
|
||||
__all__: List[str] = [
|
||||
# Plugin
|
||||
|
@ -84,4 +97,18 @@ __all__: List[str] = [
|
|||
"WoxPreviewScrollPosition",
|
||||
# Result
|
||||
"ResultTailType",
|
||||
# MRU
|
||||
"MRUData",
|
||||
"MRURestoreCallback",
|
||||
# Settings
|
||||
"PluginSettingDefinitionItem",
|
||||
"PluginSettingDefinitionType",
|
||||
"PluginSettingDefinitionValue",
|
||||
"PluginSettingValueStyle",
|
||||
"PluginSettingValueTextBox",
|
||||
"PluginSettingValueCheckBox",
|
||||
"PluginSettingValueLabel",
|
||||
"create_textbox_setting",
|
||||
"create_checkbox_setting",
|
||||
"create_label_setting",
|
||||
]
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from typing import Protocol, Callable, Dict, List
|
||||
from typing import Protocol, Callable, Dict, List, Optional
|
||||
|
||||
from .models.query import MetadataCommand
|
||||
from .models.context import Context
|
||||
from .models.query import ChangeQueryParam
|
||||
from .models.ai import AIModel, Conversation, ChatStreamCallback
|
||||
from .models.mru import MRUData
|
||||
from .models.result import Result
|
||||
from .models.setting import PluginSettingDefinitionItem
|
||||
|
||||
|
||||
class PublicAPI(Protocol):
|
||||
|
@ -45,7 +48,7 @@ class PublicAPI(Protocol):
|
|||
"""Register setting change callback"""
|
||||
...
|
||||
|
||||
async def on_get_dynamic_setting(self, ctx: Context, callback: Callable[[str], str]) -> None:
|
||||
async def on_get_dynamic_setting(self, ctx: Context, callback: Callable[[str], PluginSettingDefinitionItem]) -> None:
|
||||
"""Register dynamic setting callback"""
|
||||
...
|
||||
|
||||
|
@ -81,3 +84,13 @@ class PublicAPI(Protocol):
|
|||
- data: str, the stream content
|
||||
"""
|
||||
...
|
||||
|
||||
async def on_mru_restore(self, ctx: Context, callback: Callable[[MRUData], Optional[Result]]) -> None:
|
||||
"""Register MRU restore callback
|
||||
|
||||
Args:
|
||||
ctx: Context
|
||||
callback: Callback function that takes MRUData and returns Result or None
|
||||
Return None if the MRU data is no longer valid
|
||||
"""
|
||||
...
|
||||
|
|
|
@ -36,6 +36,11 @@ class WoxImage:
|
|||
def from_json(cls, json_str: str) -> "WoxImage":
|
||||
"""Create from JSON string with camelCase naming"""
|
||||
data = json.loads(json_str)
|
||||
return cls.from_dict(data)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "WoxImage":
|
||||
"""Create from dictionary with camelCase naming"""
|
||||
if not data.get("ImageType"):
|
||||
data["ImageType"] = WoxImageType.ABSOLUTE
|
||||
|
||||
|
@ -44,6 +49,13 @@ class WoxImage:
|
|||
image_data=data.get("ImageData", ""),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"ImageData": self.image_data,
|
||||
"ImageType": self.image_type,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def new_base64(cls, data: str) -> "WoxImage":
|
||||
"""Create a new base64 image"""
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional, Callable, TYPE_CHECKING
|
||||
from .image import WoxImage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .result import Result
|
||||
|
||||
|
||||
@dataclass
|
||||
class MRUData:
|
||||
"""MRU (Most Recently Used) data structure"""
|
||||
plugin_id: str
|
||||
title: str
|
||||
sub_title: str
|
||||
icon: WoxImage
|
||||
context_data: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> 'MRUData':
|
||||
"""Create MRUData from dictionary"""
|
||||
return cls(
|
||||
plugin_id=data.get('PluginID', ''),
|
||||
title=data.get('Title', ''),
|
||||
sub_title=data.get('SubTitle', ''),
|
||||
icon=WoxImage.from_dict(data.get('Icon', {})),
|
||||
context_data=data.get('ContextData', '')
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert MRUData to dictionary"""
|
||||
return {
|
||||
'PluginID': self.plugin_id,
|
||||
'Title': self.title,
|
||||
'SubTitle': self.sub_title,
|
||||
'Icon': self.icon.to_dict(),
|
||||
'ContextData': self.context_data
|
||||
}
|
||||
|
||||
|
||||
# Type alias for MRU restore callback
|
||||
# Note: We use forward reference to avoid circular import
|
||||
MRURestoreCallback = Callable[['MRUData'], Optional['Result']]
|
|
@ -0,0 +1,242 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Any, List
|
||||
from enum import Enum
|
||||
import json
|
||||
|
||||
|
||||
class PluginSettingDefinitionType(str, Enum):
|
||||
"""Plugin setting definition type enum"""
|
||||
HEAD = "head"
|
||||
TEXTBOX = "textbox"
|
||||
CHECKBOX = "checkbox"
|
||||
SELECT = "select"
|
||||
LABEL = "label"
|
||||
NEWLINE = "newline"
|
||||
TABLE = "table"
|
||||
DYNAMIC = "dynamic"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingValueStyle:
|
||||
"""Style configuration for plugin settings"""
|
||||
padding_left: int = field(default=0)
|
||||
padding_top: int = field(default=0)
|
||||
padding_right: int = field(default=0)
|
||||
padding_bottom: int = field(default=0)
|
||||
width: int = field(default=0)
|
||||
label_width: int = field(default=0)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"PaddingLeft": self.padding_left,
|
||||
"PaddingTop": self.padding_top,
|
||||
"PaddingRight": self.padding_right,
|
||||
"PaddingBottom": self.padding_bottom,
|
||||
"Width": self.width,
|
||||
"LabelWidth": self.label_width,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'PluginSettingValueStyle':
|
||||
"""Create from dictionary with camelCase naming"""
|
||||
return cls(
|
||||
padding_left=data.get('PaddingLeft', 0),
|
||||
padding_top=data.get('PaddingTop', 0),
|
||||
padding_right=data.get('PaddingRight', 0),
|
||||
padding_bottom=data.get('PaddingBottom', 0),
|
||||
width=data.get('Width', 0),
|
||||
label_width=data.get('LabelWidth', 0),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingDefinitionValue:
|
||||
"""Base class for plugin setting values"""
|
||||
key: str
|
||||
default_value: str = field(default="")
|
||||
|
||||
def get_key(self) -> str:
|
||||
"""Get the setting key"""
|
||||
return self.key
|
||||
|
||||
def get_default_value(self) -> str:
|
||||
"""Get the default value"""
|
||||
return self.default_value
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary"""
|
||||
return {
|
||||
"Key": self.key,
|
||||
"DefaultValue": self.default_value,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingValueTextBox(PluginSettingDefinitionValue):
|
||||
"""Text box setting value"""
|
||||
label: str = field(default="")
|
||||
suffix: str = field(default="")
|
||||
tooltip: str = field(default="")
|
||||
max_lines: int = field(default=1)
|
||||
style: PluginSettingValueStyle = field(default_factory=PluginSettingValueStyle)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"Key": self.key,
|
||||
"Label": self.label,
|
||||
"Suffix": self.suffix,
|
||||
"DefaultValue": self.default_value,
|
||||
"Tooltip": self.tooltip,
|
||||
"MaxLines": self.max_lines,
|
||||
"Style": self.style.to_dict(),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingValueCheckBox(PluginSettingDefinitionValue):
|
||||
"""Checkbox setting value"""
|
||||
label: str = field(default="")
|
||||
tooltip: str = field(default="")
|
||||
style: PluginSettingValueStyle = field(default_factory=PluginSettingValueStyle)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"Key": self.key,
|
||||
"Label": self.label,
|
||||
"DefaultValue": self.default_value,
|
||||
"Tooltip": self.tooltip,
|
||||
"Style": self.style.to_dict(),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingValueLabel(PluginSettingDefinitionValue):
|
||||
"""Label setting value"""
|
||||
content: str = field(default="")
|
||||
tooltip: str = field(default="")
|
||||
style: PluginSettingValueStyle = field(default_factory=PluginSettingValueStyle)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"Content": self.content,
|
||||
"Tooltip": self.tooltip,
|
||||
"Style": self.style.to_dict(),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginSettingDefinitionItem:
|
||||
"""Plugin setting definition item"""
|
||||
type: PluginSettingDefinitionType
|
||||
value: PluginSettingDefinitionValue
|
||||
disabled_in_platforms: List[str] = field(default_factory=list)
|
||||
is_platform_specific: bool = field(default=False)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert to dictionary with camelCase naming"""
|
||||
return {
|
||||
"Type": self.type,
|
||||
"Value": self.value.to_dict(),
|
||||
"DisabledInPlatforms": self.disabled_in_platforms,
|
||||
"IsPlatformSpecific": self.is_platform_specific,
|
||||
}
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Convert to JSON string"""
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'PluginSettingDefinitionItem':
|
||||
"""Create from dictionary"""
|
||||
setting_type = PluginSettingDefinitionType(data.get('Type', 'textbox'))
|
||||
|
||||
# Create appropriate value object based on type
|
||||
value_data = data.get('Value', {})
|
||||
value: PluginSettingDefinitionValue
|
||||
if setting_type == PluginSettingDefinitionType.TEXTBOX:
|
||||
value = PluginSettingValueTextBox(
|
||||
key=value_data.get('Key', ''),
|
||||
label=value_data.get('Label', ''),
|
||||
suffix=value_data.get('Suffix', ''),
|
||||
default_value=value_data.get('DefaultValue', ''),
|
||||
tooltip=value_data.get('Tooltip', ''),
|
||||
max_lines=value_data.get('MaxLines', 1),
|
||||
style=PluginSettingValueStyle.from_dict(value_data.get('Style', {}))
|
||||
)
|
||||
elif setting_type == PluginSettingDefinitionType.CHECKBOX:
|
||||
value = PluginSettingValueCheckBox(
|
||||
key=value_data.get('Key', ''),
|
||||
label=value_data.get('Label', ''),
|
||||
default_value=value_data.get('DefaultValue', ''),
|
||||
tooltip=value_data.get('Tooltip', ''),
|
||||
style=PluginSettingValueStyle.from_dict(value_data.get('Style', {}))
|
||||
)
|
||||
elif setting_type == PluginSettingDefinitionType.LABEL:
|
||||
value = PluginSettingValueLabel(
|
||||
key=value_data.get('Key', ''),
|
||||
content=value_data.get('Content', ''),
|
||||
tooltip=value_data.get('Tooltip', ''),
|
||||
style=PluginSettingValueStyle.from_dict(value_data.get('Style', {}))
|
||||
)
|
||||
else:
|
||||
# Default to basic value
|
||||
value = PluginSettingDefinitionValue(
|
||||
key=value_data.get('Key', ''),
|
||||
default_value=value_data.get('DefaultValue', '')
|
||||
)
|
||||
|
||||
return cls(
|
||||
type=setting_type,
|
||||
value=value,
|
||||
disabled_in_platforms=data.get('DisabledInPlatforms', []),
|
||||
is_platform_specific=data.get('IsPlatformSpecific', False)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> 'PluginSettingDefinitionItem':
|
||||
"""Create from JSON string"""
|
||||
data = json.loads(json_str)
|
||||
return cls.from_dict(data)
|
||||
|
||||
|
||||
# Helper functions for creating common setting types
|
||||
def create_textbox_setting(key: str, label: str, default_value: str = "", tooltip: str = "") -> PluginSettingDefinitionItem:
|
||||
"""Create a textbox setting"""
|
||||
return PluginSettingDefinitionItem(
|
||||
type=PluginSettingDefinitionType.TEXTBOX,
|
||||
value=PluginSettingValueTextBox(
|
||||
key=key,
|
||||
label=label,
|
||||
default_value=default_value,
|
||||
tooltip=tooltip
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_checkbox_setting(key: str, label: str, default_value: str = "false", tooltip: str = "") -> PluginSettingDefinitionItem:
|
||||
"""Create a checkbox setting"""
|
||||
return PluginSettingDefinitionItem(
|
||||
type=PluginSettingDefinitionType.CHECKBOX,
|
||||
value=PluginSettingValueCheckBox(
|
||||
key=key,
|
||||
label=label,
|
||||
default_value=default_value,
|
||||
tooltip=tooltip
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_label_setting(content: str, tooltip: str = "") -> PluginSettingDefinitionItem:
|
||||
"""Create a label setting"""
|
||||
return PluginSettingDefinitionItem(
|
||||
type=PluginSettingDefinitionType.LABEL,
|
||||
value=PluginSettingValueLabel(
|
||||
key="", # Labels don't need keys
|
||||
content=content,
|
||||
tooltip=tooltip
|
||||
)
|
||||
)
|
|
@ -624,7 +624,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "wox-plugin"
|
||||
version = "0.0.48"
|
||||
version = "0.0.49"
|
||||
source = { editable = "." }
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
Loading…
Reference in New Issue