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:
qianlifeng 2025-07-30 21:22:37 +08:00
parent 4b2018934d
commit 62fdd3b7c7
No known key found for this signature in database
21 changed files with 501 additions and 57 deletions

View File

@ -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)
}

View File

@ -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":

View File

@ -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()

View File

@ -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

View File

@ -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",

View File

@ -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:

View File

@ -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
}
}

View File

@ -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 })
}
}

View File

@ -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"

View File

@ -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

View File

@ -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})

View File

@ -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" },
]

View File

@ -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",

View File

@ -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"

View File

@ -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"

View File

@ -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",
]

View File

@ -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
"""
...

View File

@ -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"""

View File

@ -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']]

View File

@ -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
)
)

View File

@ -624,7 +624,7 @@ wheels = [
[[package]]
name = "wox-plugin"
version = "0.0.48"
version = "0.0.49"
source = { editable = "." }
[package.optional-dependencies]