diff --git a/Wox.Plugin.Host.Nodejs/src/jsonrpc.ts b/Wox.Plugin.Host.Nodejs/src/jsonrpc.ts index ee62b29e1..367d439c1 100644 --- a/Wox.Plugin.Host.Nodejs/src/jsonrpc.ts +++ b/Wox.Plugin.Host.Nodejs/src/jsonrpc.ts @@ -250,9 +250,11 @@ async function action(ctx: Context, request: PluginJsonRpcRequest) { return } - return pluginAction({ + pluginAction({ ContextData: request.Params.ContextData }) + + return } async function refresh(ctx: Context, request: PluginJsonRpcRequest) { diff --git a/Wox.Plugin.Host.Python/host.py b/Wox.Plugin.Host.Python/host.py index 5c13b1561..286b6a49c 100644 --- a/Wox.Plugin.Host.Python/host.py +++ b/Wox.Plugin.Host.Python/host.py @@ -4,6 +4,7 @@ import json import uuid from typing import Dict, Any +import traceback import websockets import logger @@ -14,9 +15,13 @@ async def handle_message(ws: websockets.WebSocketServerProtocol, message: str): """Handle incoming WebSocket message""" + + trace_id = str(uuid.uuid4()) try: msg_data = json.loads(message) - trace_id = msg_data.get("TraceId", str(uuid.uuid4())) + if msg_data.get("TraceId"): + trace_id = msg_data.get("TraceId") + ctx = new_context_with_value("traceId", trace_id) if PLUGIN_JSONRPC_TYPE_RESPONSE in message: @@ -24,9 +29,9 @@ async def handle_message(ws: websockets.WebSocketServerProtocol, message: str): if msg_data.get("Id") in waiting_for_response: deferred = waiting_for_response[msg_data["Id"]] if msg_data.get("Error"): - deferred.reject(msg_data["Error"]) + deferred.set_exception(Exception(msg_data["Error"])) else: - deferred.resolve(msg_data.get("Result")) + deferred.set_result(msg_data.get("Result")) del waiting_for_response[msg_data["Id"]] elif PLUGIN_JSONRPC_TYPE_REQUEST in message: # Handle request from Wox @@ -39,8 +44,9 @@ async def handle_message(ws: websockets.WebSocketServerProtocol, message: str): "Type": PLUGIN_JSONRPC_TYPE_RESPONSE, "Result": result } - await ws.send(json.dumps(response)) + await ws.send(json.dumps(response, default=lambda o: '')) except Exception as e: + error_stack = traceback.format_exc() error_response = { "TraceId": trace_id, "Id": msg_data["Id"], @@ -48,24 +54,29 @@ async def handle_message(ws: websockets.WebSocketServerProtocol, message: str): "Type": PLUGIN_JSONRPC_TYPE_RESPONSE, "Error": str(e) } - await logger.error(trace_id, f"handle request failed: {str(e)}") - await ws.send(json.dumps(error_response)) + await logger.error(trace_id, f"handle request failed: {str(e)}\nStack trace:\n{error_stack}") + await ws.send(json.dumps(error_response, default=lambda o: '')) else: await logger.error(trace_id, f"unknown message type: {message}") except Exception as e: - await logger.error(str(uuid.uuid4()), f"receive and handle msg error: {message}, err: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(trace_id, f"receive and handle msg error: {message}, err: {str(e)}\nStack trace:\n{error_stack}") async def handler(websocket: websockets.WebSocketServerProtocol): """WebSocket connection handler""" logger.update_websocket(websocket) try: - async for message in websocket: - await handle_message(websocket, message) - except websockets.exceptions.ConnectionClosed: - await logger.info(str(uuid.uuid4()), "connection closed") - except Exception as e: - await logger.error(str(uuid.uuid4()), f"connection error: {str(e)}") + while True: + try: + message = await websocket.recv() + asyncio.create_task(handle_message(websocket, message)) + except websockets.exceptions.ConnectionClosed: + await logger.info(str(uuid.uuid4()), "connection closed") + break + except Exception as e: + error_stack = traceback.format_exc() + await logger.error(str(uuid.uuid4()), f"connection error: {str(e)}\nStack trace:\n{error_stack}") finally: logger.update_websocket(None) diff --git a/Wox.Plugin.Host.Python/jsonrpc.py b/Wox.Plugin.Host.Python/jsonrpc.py index 63bf0690c..a41190c3d 100644 --- a/Wox.Plugin.Host.Python/jsonrpc.py +++ b/Wox.Plugin.Host.Python/jsonrpc.py @@ -7,6 +7,7 @@ import zipimport import websockets import logger +import inspect from wox_plugin import ( Context, Plugin, @@ -18,9 +19,10 @@ new_context_with_value, PluginInitParams ) -from constants import PLUGIN_JSONRPC_TYPE_REQUEST, PLUGIN_JSONRPC_TYPE_RESPONSE -from plugin_manager import plugin_instances, waiting_for_response +from plugin_manager import plugin_instances from plugin_api import PluginAPI +import traceback +import asyncio async def handle_request_from_wox(ctx: Context, request: Dict[str, Any], ws: websockets.WebSocketServerProtocol) -> Any: """Handle incoming request from Wox""" @@ -88,7 +90,8 @@ async def load_plugin(ctx: Context, request: Dict[str, Any]) -> None: await logger.info(ctx["Values"]["traceId"], f"<{plugin_name}> load plugin successfully") except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin_name}> load plugin failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin_name}> load plugin failed: {str(e)}\nStack trace:\n{error_stack}") raise e async def init_plugin(ctx: Context, request: Dict[str, Any], ws: websockets.WebSocketServerProtocol) -> None: @@ -102,6 +105,8 @@ async def init_plugin(ctx: Context, request: Dict[str, Any], ws: websockets.WebS # Create plugin API instance api = PluginAPI(ws, plugin_id, plugin["name"]) plugin["api"] = api + plugin["actions"] = {} # Add actions cache + plugin["refreshes"] = {} # Add refreshes cache # Call plugin's init method if it exists if hasattr(plugin["plugin"], "init"): @@ -110,7 +115,8 @@ async def init_plugin(ctx: Context, request: Dict[str, Any], ws: websockets.WebS await logger.info(ctx["Values"]["traceId"], f"<{plugin['name']}> init plugin successfully") except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> init plugin failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> init plugin failed: {str(e)}\nStack trace:\n{error_stack}") raise e async def query(ctx: Context, request: Dict[str, Any]) -> list: @@ -124,6 +130,10 @@ async def query(ctx: Context, request: Dict[str, Any]) -> list: if not hasattr(plugin["plugin"], "query"): return [] + # Clear action and refresh caches before query + plugin["actions"].clear() + plugin["refreshes"].clear() + query_params = Query( Type=QueryType(request["Params"]["Type"]), RawQuery=request["Params"]["RawQuery"], @@ -135,8 +145,8 @@ async def query(ctx: Context, request: Dict[str, Any]) -> list: ) results = await plugin["plugin"].query(ctx, query_params) - - # Ensure each result has an ID + + # Ensure each result has an ID and cache actions and refreshes if results: for result in results: if not result.Id: @@ -145,10 +155,16 @@ async def query(ctx: Context, request: Dict[str, Any]) -> list: for action in result.Actions: if not action.Id: action.Id = str(uuid.uuid4()) + # Cache action + plugin["actions"][action.Id] = action.Action + # Cache refresh callback if exists + if hasattr(result, "RefreshInterval") and result.RefreshInterval is not None and result.RefreshInterval > 0 and hasattr(result, "OnRefresh"): + plugin["refreshes"][result.Id] = result.OnRefresh - return [result.__dict__ for result in results] if results else [] + return [result.to_dict() for result in results] except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> query failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> query failed: {str(e)}\nStack trace:\n{error_stack}") raise e async def action(ctx: Context, request: Dict[str, Any]) -> Any: @@ -162,13 +178,16 @@ async def action(ctx: Context, request: Dict[str, Any]) -> Any: action_id = request["Params"]["ActionId"] context_data = request["Params"].get("ContextData") - # Find the action in the plugin's results - if hasattr(plugin["plugin"], "handle_action"): - return await plugin["plugin"].handle_action(action_id, context_data) + # Get action from cache + action_func = plugin["actions"].get(action_id) + if action_func: + # Don't await the action, let it run independently + asyncio.create_task(action_func({"ContextData": context_data})) return None except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> action failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> action failed: {str(e)}\nStack trace:\n{error_stack}") raise e async def refresh(ctx: Context, request: Dict[str, Any]) -> Any: @@ -180,14 +199,30 @@ async def refresh(ctx: Context, request: Dict[str, Any]) -> Any: try: result_id = request["Params"]["ResultId"] - - # Find the refresh callback in the plugin's results - if hasattr(plugin["plugin"], "handle_refresh"): - return await plugin["plugin"].handle_refresh(result_id) + refreshable_result = json.loads(request["Params"]["RefreshableResult"]) + + # Get refresh callback from cache + refresh_func = plugin["refreshes"].get(result_id) + if refresh_func: + refreshed_result = await refresh_func(refreshable_result) + + # Cache any new actions from the refreshed result + if refreshed_result.Actions: + for action in refreshed_result.Actions: + if not action.Id: + action.Id = str(uuid.uuid4()) + plugin["actions"][action.Id] = action.Action + + # Cache refresh callback if exists + if hasattr(refreshed_result, "RefreshInterval") and refreshed_result.RefreshInterval is not None and refreshed_result.RefreshInterval > 0 and hasattr(refreshed_result, "OnRefresh"): + plugin["refreshes"][result_id] = refreshed_result.OnRefresh + + return refreshed_result.to_dict() return None except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> refresh failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> refresh failed: {str(e)}\nStack trace:\n{error_stack}") raise e async def unload_plugin(ctx: Context, request: Dict[str, Any]) -> None: @@ -211,5 +246,6 @@ async def unload_plugin(ctx: Context, request: Dict[str, Any]) -> None: await logger.info(ctx["Values"]["traceId"], f"<{plugin['name']}> unload plugin successfully") except Exception as e: - await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> unload plugin failed: {str(e)}") + error_stack = traceback.format_exc() + await logger.error(ctx["Values"]["traceId"], f"<{plugin['name']}> unload plugin failed: {str(e)}\nStack trace:\n{error_stack}") raise e \ No newline at end of file diff --git a/Wox.Plugin.Host.Python/plugin_api.py b/Wox.Plugin.Host.Python/plugin_api.py index bdc472293..f289f3891 100644 --- a/Wox.Plugin.Host.Python/plugin_api.py +++ b/Wox.Plugin.Host.Python/plugin_api.py @@ -14,7 +14,8 @@ Conversation, ChatStreamFunc, ) -from jsonrpc import PLUGIN_JSONRPC_TYPE_REQUEST, waiting_for_response +from constants import PLUGIN_JSONRPC_TYPE_REQUEST +from plugin_manager import waiting_for_response class PluginAPI(PublicAPI): def __init__(self, ws: websockets.WebSocketServerProtocol, plugin_id: str, plugin_name: str): diff --git a/Wox.Plugin.Host.Python/pyproject.toml b/Wox.Plugin.Host.Python/pyproject.toml index 9099afa2a..272cf0b66 100644 --- a/Wox.Plugin.Host.Python/pyproject.toml +++ b/Wox.Plugin.Host.Python/pyproject.toml @@ -5,7 +5,7 @@ description = "Python host for Wox plugins" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "wox-plugin>=0.1.0", "loguru", "websockets", + "wox-plugin==0.0.24", ] diff --git a/Wox.Plugin.Host.Python/requirements.txt b/Wox.Plugin.Host.Python/requirements.txt deleted file mode 100644 index 42558a3c7..000000000 --- a/Wox.Plugin.Host.Python/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -loguru==0.7.2 -websockets==14.1 -wox-plugin==0.1.0 diff --git a/Wox.Plugin.Host.Python/uv.lock b/Wox.Plugin.Host.Python/uv.lock index 2b2a63cce..dae2af285 100644 --- a/Wox.Plugin.Host.Python/uv.lock +++ b/Wox.Plugin.Host.Python/uv.lock @@ -12,15 +12,15 @@ wheels = [ [[package]] name = "loguru" -version = "0.7.2" +version = "0.7.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "win32-setctime", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/30/d87a423766b24db416a46e9335b9602b054a72b96a88a241f2b09b560fa8/loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac", size = 145103 } +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/0a/4f6fed21aa246c6b49b561ca55facacc2a44b87d65b8b92362a8e99ba202/loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb", size = 62549 }, + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, ] [[package]] @@ -93,11 +93,11 @@ wheels = [ [[package]] name = "wox-plugin" -version = "0.1.0" +version = "0.0.24" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/8c/a956b1c7ce61baeb1edd17c51527dfb1060d301ae04067fe3f6645783ca8/wox_plugin-0.1.0.tar.gz", hash = "sha256:b447e793dae81c5de073537f22c432ce11da87e56738483916d1ffba539ed753", size = 4011 } +sdist = { url = "https://files.pythonhosted.org/packages/41/75/12281bce4270c4712ec8c77f5024bcf86b0ee7ff695be31798fbc36d8368/wox_plugin-0.0.24.tar.gz", hash = "sha256:2f0a10330ec8d380ffc952b57b7803fbc38d4416123f28333ee2d9f9bbcf15cb", size = 4592 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/b0/2ac0c4796dc50f198a4ebb06b5d73021d741cf0022473d469e9e2f467ccd/wox_plugin-0.1.0-py3-none-any.whl", hash = "sha256:6b048f7ae55df7bf4901cd34f24f96c21341ddd2b6707184d9970f279d35f3b0", size = 4067 }, + { url = "https://files.pythonhosted.org/packages/0c/80/6b2f988cefea7f6dd34b10f28ed9cdc534a178b01aca477802b5cb2363ae/wox_plugin-0.0.24-py3-none-any.whl", hash = "sha256:730c39117b752b521e70f9f5acffc18af2ab70e8985c66f17dba9d450cc21b50", size = 4751 }, ] [[package]] @@ -114,5 +114,5 @@ dependencies = [ requires-dist = [ { name = "loguru" }, { name = "websockets" }, - { name = "wox-plugin", specifier = ">=0.1.0" }, + { name = "wox-plugin", specifier = "==0.0.24" }, ] diff --git a/Wox.Plugin.Python/Makefile b/Wox.Plugin.Python/Makefile index 736e06eb8..796706b4f 100644 --- a/Wox.Plugin.Python/Makefile +++ b/Wox.Plugin.Python/Makefile @@ -1,2 +1,2 @@ -publish: +release: python publish.py patch \ No newline at end of file diff --git a/Wox.Plugin.Python/publish.py b/Wox.Plugin.Python/publish.py index b2a99c26f..dbf5fbdfd 100644 --- a/Wox.Plugin.Python/publish.py +++ b/Wox.Plugin.Python/publish.py @@ -11,7 +11,7 @@ def run_command(command: str) -> int: return subprocess.call(command, shell=True) def update_version(version_type: str) -> str: - """Update version number + """Update version number in setup.py, pyproject.toml and __init__.py version_type: major, minor, or patch """ # Read setup.py @@ -47,6 +47,16 @@ def update_version(version_type: str) -> str: ) setup_path.write_text(new_content) + # Update pyproject.toml + pyproject_path = Path("pyproject.toml") + pyproject_content = pyproject_path.read_text() + new_pyproject_content = re.sub( + r'version = "(\d+)\.(\d+)\.(\d+)"', + f'version = "{new_version}"', + pyproject_content + ) + pyproject_path.write_text(new_pyproject_content) + # Update __init__.py init_path = Path("wox_plugin/__init__.py") init_content = init_path.read_text() diff --git a/Wox.Plugin.Python/pyproject.toml b/Wox.Plugin.Python/pyproject.toml index b0852067e..b894f609e 100644 --- a/Wox.Plugin.Python/pyproject.toml +++ b/Wox.Plugin.Python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "wox-plugin" -version = "0.1.0" +version = "0.0.24" description = "Python types for Wox plugins" readme = "README.md" requires-python = ">=3.12" diff --git a/Wox.Plugin.Python/setup.py b/Wox.Plugin.Python/setup.py index 487637ea6..0a217ea82 100644 --- a/Wox.Plugin.Python/setup.py +++ b/Wox.Plugin.Python/setup.py @@ -2,7 +2,7 @@ setup( name="wox-plugin", - version="0.0.18", + version="0.0.24", description="All Python plugins for Wox should use types in this package", long_description=open("README.md").read(), long_description_content_type="text/markdown", @@ -10,6 +10,7 @@ author_email="", url="https://github.com/Wox-launcher/Wox", packages=find_packages(), + package_data={"wox_plugin": ["py.typed"]}, install_requires=[ "typing_extensions>=4.0.0; python_version < '3.8'" ], diff --git a/Wox.Plugin.Python/uv.lock b/Wox.Plugin.Python/uv.lock index 4331cbe91..4704a89bf 100644 --- a/Wox.Plugin.Python/uv.lock +++ b/Wox.Plugin.Python/uv.lock @@ -422,8 +422,8 @@ wheels = [ ] [[package]] -name = "wox-plugin-python" -version = "0.1.0" +name = "wox-plugin" +version = "0.0.20" source = { virtual = "." } [package.optional-dependencies] diff --git a/Wox.Plugin.Python/wox_plugin/__init__.py b/Wox.Plugin.Python/wox_plugin/__init__.py index ec0c8d34f..b78443824 100644 --- a/Wox.Plugin.Python/wox_plugin/__init__.py +++ b/Wox.Plugin.Python/wox_plugin/__init__.py @@ -52,4 +52,4 @@ PluginInitParams, ) -__version__ = "0.0.18" \ No newline at end of file +__version__ = "0.0.24" \ No newline at end of file diff --git a/Wox.Plugin.Python/wox_plugin/py.typed b/Wox.Plugin.Python/wox_plugin/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/Wox.Plugin.Python/wox_plugin/types.py b/Wox.Plugin.Python/wox_plugin/types.py index 55831c0d5..d94862541 100644 --- a/Wox.Plugin.Python/wox_plugin/types.py +++ b/Wox.Plugin.Python/wox_plugin/types.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Dict, List, Optional, Protocol, Union, Callable, Any, TypedDict, Literal +from typing import Dict, List, Optional, Protocol, Union, Callable, Any, TypedDict, Literal, Awaitable import uuid # Basic types @@ -34,6 +34,16 @@ class Selection: Text: Optional[str] = None FilePaths: Optional[List[str]] = None + def to_dict(self): + return { + "Type": self.Type, + "Text": self.Text, + "FilePaths": self.FilePaths + } + + def __dict__(self): + return self.to_dict() + # Query Environment @dataclass class QueryEnv: @@ -53,6 +63,16 @@ class QueryEnv: """ ActiveBrowserUrl: str + def to_dict(self): + return { + "ActiveWindowTitle": self.ActiveWindowTitle, + "ActiveWindowPid": self.ActiveWindowPid, + "ActiveBrowserUrl": self.ActiveBrowserUrl + } + + def __dict__(self): + return self.to_dict() + # Query class QueryType(str, Enum): INPUT = "input" @@ -68,6 +88,20 @@ class Query: Selection: Selection Env: QueryEnv + def to_dict(self): + return { + "Type": self.Type, + "RawQuery": self.RawQuery, + "TriggerKeyword": self.TriggerKeyword, + "Command": self.Command, + "Search": self.Search, + "Selection": self.Selection.to_dict(), + "Env": self.Env.to_dict() + } + + def __dict__(self): + return self.to_dict() + def is_global_query(self) -> bool: return self.Type == QueryType.INPUT and not self.TriggerKeyword @@ -86,6 +120,15 @@ class WoxImage: ImageType: WoxImageType ImageData: str + def to_dict(self): + return { + "ImageType": self.ImageType, + "ImageData": self.ImageData + } + + def __dict__(self): + return self.to_dict() + def new_base64_wox_image(image_data: str) -> WoxImage: return WoxImage(ImageType=WoxImageType.BASE64, ImageData=image_data) @@ -102,6 +145,16 @@ class WoxPreview: PreviewData: str PreviewProperties: Dict[str, str] + def to_dict(self): + return { + "PreviewType": self.PreviewType, + "PreviewData": self.PreviewData, + "PreviewProperties": self.PreviewProperties + } + + def __dict__(self): + return self.to_dict() + class ResultTailType(str, Enum): TEXT = "text" IMAGE = "image" @@ -112,35 +165,77 @@ class ResultTail: Text: Optional[str] = None Image: Optional[WoxImage] = None + def to_dict(self): + return { + "Type": self.Type, + "Text": self.Text, + "Image": self.Image.to_dict() if self.Image else None + } + + def __dict__(self): + return self.to_dict() + @dataclass class ActionContext: ContextData: str @dataclass class ResultAction: - Id: Optional[str] Name: str - Icon: Optional[WoxImage] - IsDefault: Optional[bool] - PreventHideAfterAction: Optional[bool] - Action: Callable[[ActionContext], None] - Hotkey: Optional[str] + Action: Callable[[ActionContext], Awaitable[None]] + Id: Optional[str] = None + Icon: Optional[WoxImage] = None + IsDefault: Optional[bool] = None + PreventHideAfterAction: Optional[bool] = None + Hotkey: Optional[str] = None + + def to_dict(self): + return { + "Name": self.Name, + "Id": self.Id, + "Icon": self.Icon.to_dict() if self.Icon else None, + "IsDefault": self.IsDefault, + "PreventHideAfterAction": self.PreventHideAfterAction, + "Hotkey": self.Hotkey + } + + def __dict__(self): + return self.to_dict() @dataclass class Result: - Id: Optional[str] Title: str - SubTitle: Optional[str] Icon: WoxImage - Preview: Optional[WoxPreview] - Score: Optional[float] - Group: Optional[str] - GroupScore: Optional[float] - Tails: Optional[List[ResultTail]] - ContextData: Optional[str] - Actions: Optional[List[ResultAction]] - RefreshInterval: Optional[int] - OnRefresh: Optional[Callable[["RefreshableResult"], "RefreshableResult"]] + Id: Optional[str] = None + SubTitle: Optional[str] = None + Preview: Optional[WoxPreview] = None + Score: Optional[float] = None + Group: Optional[str] = None + GroupScore: Optional[float] = None + Tails: Optional[List[ResultTail]] = None + ContextData: Optional[str] = None + Actions: Optional[List[ResultAction]] = None + RefreshInterval: Optional[int] = None + OnRefresh: Optional[Callable[["RefreshableResult"], "RefreshableResult"]] = None + + def to_dict(self): + return { + "Title": self.Title, + "Icon": self.Icon.to_dict(), + "Id": self.Id, + "SubTitle": self.SubTitle, + "Preview": self.Preview.to_dict() if self.Preview else None, + "Score": self.Score, + "Group": self.Group, + "GroupScore": self.GroupScore, + "Tails": [tail.to_dict() for tail in self.Tails] if self.Tails else None, + "ContextData": self.ContextData, + "Actions": [action.to_dict() for action in self.Actions] if self.Actions else None, + "RefreshInterval": self.RefreshInterval + } + + def __dict__(self): + return self.to_dict() @dataclass class RefreshableResult: @@ -153,12 +248,37 @@ class RefreshableResult: RefreshInterval: int Actions: List[ResultAction] + def to_dict(self): + return { + "Title": self.Title, + "SubTitle": self.SubTitle, + "Icon": self.Icon.to_dict(), + "Preview": self.Preview.to_dict(), + "Tails": [tail.to_dict() for tail in self.Tails], + "ContextData": self.ContextData, + "RefreshInterval": self.RefreshInterval, + "Actions": [action.to_dict() for action in self.Actions] + } + + def __dict__(self): + return self.to_dict() + # Plugin API @dataclass class ChangeQueryParam: QueryType: QueryType - QueryText: Optional[str] - QuerySelection: Optional[Selection] + QueryText: Optional[str] = None + QuerySelection: Optional[Selection] = None + + def to_dict(self): + return { + "QueryType": self.QueryType, + "QueryText": self.QueryText, + "QuerySelection": self.QuerySelection.to_dict() if self.QuerySelection else None + } + + def __dict__(self): + return self.to_dict() # AI class ConversationRole(str, Enum): @@ -176,6 +296,16 @@ class Conversation: Text: str Timestamp: int + def to_dict(self): + return { + "Role": self.Role, + "Text": self.Text, + "Timestamp": self.Timestamp + } + + def __dict__(self): + return self.to_dict() + ChatStreamFunc = Callable[[ChatStreamDataType, str], None] # Settings