Plugin Specification (Code Assets & Navigation)
This page keeps only copy-paste code assets: the Prompt-and-Go template, the standalone-development SDK stub, the 7 complete runnable templates, and the AI implementation checklist.
All prose contracts (directory layout /
mod.jsonfield table /typeselection /entrypoint/config_schema/ base-class signatures / Host SPI / model artifacts / runtime dependencies / UI slots / runtime environment and red lines …) have been split into knowledge cards — start at the Plugin Development Docs hub: one topic per page, every fact written exactly once.The single source of truth for the contract is the client source code (
src/transbox/plugins/,src/transbox/ui/bridge/): if a page disagrees with the source, the source wins. This page anddocs/plugin-dev/are kept in sync.
0. Direct Prompt for AI Agents (Prompt-and-Go Template)
Companion reading: paste this page's §1 (SDK stub) plus the matching type card from the Plugin Development Docs into the AI together — the result drops straight into a plugin folder.
If you want Large Language Models (such as ChatGPT, Claude, or DeepSeek) to develop and generate a 100% compliant, ready-to-run TransBox plugin without having access to the TransBox source code, copy and send the following system prompt along with this document to the AI:
You are a senior TransBox plugin development engineer. Follow the "TransBox Plugin Specification & Host Contract" strictly to develop a fully compliant plugin without access to the host application's private codebase.
[Goal & Requirements]:
<Describe your desired functionality here, e.g. integrating a cloud ASR/MT/TTS provider, building an interview copilot assistant, creating a specialized glossary, etc.>
[Mandatory Rules & Constraints]:
1. Directory Structure: Output a standalone directory with a valid mod.json at its root. id must be globally unique (format: author.type.name, never use builtin.*);
2. Contract Inheritance: Python plugins must inherit from the corresponding base class in transbox.plugins.base (BaseASRProvider / BaseMTEngineProvider / BaseTTSProvider / BaseAssistantPlugin / BaseInterpretationProvider / BasePipelineMiddleware / BaseToolPlugin) and implement all abstract methods with strictly matching signatures;
3. Thread Safety & Async: Any long-running tasks, network requests, or LLM inferences MUST run asynchronously via self.run_in_background() and emit stream events via self.emit_event(). Never block the Qt main UI thread;
4. Dynamic Settings: Declare schema in mod.json under config_schema and read values in initialize()/update_config() via self._config.get(). Never hardcode private keys or local paths;
5. Self-Contained Locales: If UI strings or options exist, provide localized JSONs inside locales/ (zh-CN.json and en-US.json) prefixed with plugin.<plugin_id>.*. Never modify core application locale files;
6. Design Guidelines: For QML views, use ThemeTokens and PluggableWindowBackground to adapt to dark/light themes. Never use emojis or raw text characters as icons; always use vector SVGs.
Please output:
1. Complete directory tree;
2. Complete mod.json manifest;
3. Complete Python / QML / JSON code (no missing core logic or placeholders);
4. Local testing and Workshop publishing instructions.1. Standalone Development Stub (For AI & External Dev)
Key Convention for AI Agents & External Developers: When a plugin runs inside TransBox, the runtime automatically provides the
transbox.plugins.basepackage. If you are developing in a standalone environment without the host repository (e.g., standard virtual environments, CI linters, or standalone unit tests), use this fallback import pattern or save it astransbox_stub.py:
try:
from transbox.plugins.base import (
BasePlugin, BaseASRProvider, BaseMTEngineProvider, BaseTTSProvider,
BaseAssistantPlugin, BaseInterpretationProvider, BasePipelineMiddleware,
BaseToolPlugin, Transcript, Translation, PipelineContext, ASRCapabilities
)
from transbox.plugins.manifest import ModManifest
except ImportError:
# ── Lightweight self-contained stub for standalone testing & AI coding ──
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
import numpy as np
@dataclass
class ModManifest:
id: str = "demo.plugin"
name: str = "Demo"
version: str = "1.0.0"
type: str = "mt"
@dataclass
class Transcript:
text: str
language: Optional[str] = None
words: Optional[List[Any]] = None
@dataclass
class Translation:
text: str
source_prefix: Optional[str] = None
complete: bool = True
@dataclass
class PipelineContext:
session_id: str = ""
source: str = "peer"
source_lang: str = "auto"
target_lang: str = "zh"
class BasePlugin(ABC):
def __init__(self, manifest: Any) -> None:
self.manifest = manifest
self.host: Any = None
self._config: Dict[str, Any] = {}
@property
def id(self) -> str: return getattr(self.manifest, "id", "")
def initialize(self, config: Optional[Dict[str, Any]] = None) -> None:
self._config = config or {}
def shutdown(self) -> None: pass
def update_config(self, new_config: Dict[str, Any]) -> None:
self._config.update(new_config)
def run_in_background(self, target: Callable, *args: Any, **kwargs: Any) -> Any:
import threading
t = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=True)
t.start()
return t
def emit_event(self, event_name: str, payload: Any = None) -> None: pass
class BaseMTEngineProvider(BasePlugin):
@abstractmethod
def translate(self, text: str, source_lang: str = "en", target_lang: str = "zh", **kwargs: Any) -> Translation: ...
class BaseASRProvider(BasePlugin):
@abstractmethod
def transcribe(self, audio: np.ndarray, sample_rate: int = 16000, language: str = "auto", **kwargs: Any) -> Transcript: ...
class BaseTTSProvider(BasePlugin):
supports_voice_clone: bool = False
supports_speed: bool = False
def ensure_loaded(self) -> None: ...
@abstractmethod
def synthesize(self, text: str, voice_id: str = "", language: str = "zh") -> Tuple[np.ndarray, int]: ...
def stream_tts(self, text: str, voice_id: str = "", language: str = "zh"): ...
def list_voices(self) -> List[Dict[str, str]]: ...
def register_voice(self, voice_name: str, audio_samples: np.ndarray,
reference_text: str = "", sample_rate: int = 24000) -> bool: ...
def remove_voice(self, voice_name: str) -> bool: ...
class BaseAssistantPlugin(BasePlugin): pass
class BasePipelineMiddleware(BasePlugin): pass
class BaseInterpretationProvider(BasePlugin): pass
class BaseToolPlugin(BasePlugin): pass2. Minimal Runnable Templates
2.1 Custom MT (rename and use as-is)
mod.json:
{
"id": "myteam.mt.demo",
"name": "我的示范翻译引擎",
"version": "1.0.0",
"author": "Me",
"type": "mt",
"entrypoint": "my_translator.py:MyMTProvider",
"config_schema": [
{
"key": "prefix_tag",
"label": "译文前缀",
"type": "string",
"default": "【Demo】"
}
]
}my_translator.py:
from __future__ import annotations
from typing import Any, Dict, Optional
from transbox.plugins.base import BaseMTEngineProvider, Translation
from transbox.plugins.manifest import ModManifest
class MyMTProvider(BaseMTEngineProvider):
def __init__(self, manifest: ModManifest) -> None:
super().__init__(manifest)
self.prefix = "【Demo】"
def initialize(self, config: Optional[Dict[str, Any]] = None) -> None:
super().initialize(config)
self.prefix = self._config.get("prefix_tag", "【Demo】")
def translate(
self,
text: str,
source_lang: str = "en",
target_lang: str = "zh",
glossary_context: Optional[str] = None,
target: Optional[str] = None,
source: Optional[str] = None,
) -> Translation:
tgt = target or target_lang
clean = (text or "").strip()
return Translation(text=f"{self.prefix} {clean} → {tgt}", complete=True)After enabling: Settings → Machine Translation → select this engine in the engine dropdown.
2.2 Middleware
from transbox.plugins.base import BasePipelineMiddleware, Transcript, Translation
class TextFilterMiddleware(BasePipelineMiddleware):
@property
def priority(self) -> int:
return 50
def process_transcript(self, transcript: Transcript, context) -> Transcript:
text = transcript.text.replace("那个...", "")
return Transcript(text=text, language=transcript.language)
def process_translation(self, translation: Translation, context) -> Translation:
text = translation.text.replace("坏词", "***")
return Translation(text=text, complete=translation.complete)entrypoint: "filter_middleware.py:TextFilterMiddleware"。
2.3 Custom ASR Engine (Streaming & Chunk Transcription)
mod.json:
{
"id": "myteam.asr.demo",
"name": "Demo ASR Engine",
"version": "1.0.0",
"type": "asr",
"entrypoint": "my_asr.py:MyASRProvider",
"config_schema": [
{
"key": "api_url",
"label": "ASR Service Endpoint",
"type": "string",
"default": "http://127.0.0.1:8000/transcribe"
}
]
}my_asr.py:
from __future__ import annotations
from typing import Any, Callable, Dict, Iterator, Optional, Tuple
import numpy as np
from transbox.plugins.base import ASRCapabilities, BaseASRProvider, Transcript, WordTime
from transbox.plugins.manifest import ModManifest
class MyASRProvider(BaseASRProvider):
def __init__(self, manifest: ModManifest) -> None:
super().__init__(manifest)
self.api_url = "http://127.0.0.1:8000/transcribe"
def initialize(self, config: Optional[Dict[str, Any]] = None) -> None:
super().initialize(config)
self.api_url = self._config.get("api_url", "http://127.0.0.1:8000/transcribe")
@property
def capabilities(self) -> ASRCapabilities:
return ASRCapabilities(
has_native_vad=True,
supports_streaming=True,
supports_word_timestamps=False,
supports_language_detection=True
)
@property
def is_streaming_capable(self) -> bool:
return True
def transcribe(
self,
audio: np.ndarray,
sample_rate: int = 16000,
language: str = "auto",
stream_id: str = "default",
with_alignment: bool = False,
on_token: Optional[Callable[[str], None]] = None,
) -> Transcript:
# Full-chunk transcription: audio is mono float32 numpy array at 16kHz in range [-1.0, 1.0]
return Transcript(text="[ASR Demo] Hello World", language="en")
def stream_open(self, stream_id: str, language: str = "auto") -> Any:
return True
def stream_feed(self, stream_id: str, pcm_chunk: np.ndarray) -> str:
return ""
def stream_events(self, stream_id: str) -> Iterator[Tuple[str, bool]]:
yield ("Hello", False)
yield ("Hello World", True)
def stream_close(self, stream_id: str) -> str:
return "Hello World"2.4 Custom TTS Engine
mod.json:
{
"id": "myteam.tts.demo",
"name": "Demo TTS",
"version": "1.0.0",
"type": "tts",
"entrypoint": "my_tts.py:MyTTSProvider",
"capabilities": {
"voices": true,
"voice_clone": true,
"voice_remove": true,
"speed": false
}
}With
capabilities.voice_clone: truethe settings page shows a "Register cloned voice" panel where users can record 1~30s of reference audio right inside the app (or pick an existing file). The host handles the microphone, WAV writing and decoding — you only implementregister_voice().
my_tts.py:
from __future__ import annotations
from typing import Any, Dict, Iterator, List, Optional, Tuple
import numpy as np
from transbox.plugins.base import BaseTTSProvider
class MyTTSProvider(BaseTTSProvider):
supports_voice_clone = True # same as mod.json capabilities, the union wins
supports_speed = False # False → host does pitch-preserving stretching for you
@property
def sample_rate(self) -> int:
return 24000
# ── Lifecycle (lazy: initialize must not start a service, see [Local-service engine contract](./plugin-dev/providers/local-service.md)) ──
def initialize(self, config=None):
super().initialize(config)
info = self.host.inspect_model_artifact(self.manifest.id, "my-tts-weights")
self._model_dir = info.get("model_dir")
def ensure_loaded(self, progress_callback=None):
"""Called by the host when the engine is actually about to be used."""
self.wait_async_init()
self._start_server_if_needed()
def check_ready(self) -> Tuple[bool, str, str]:
if not self._model_dir:
return False, "model_not_ready", "Model not ready — download it in Settings"
return True, "", "" # a stopped service still counts as usable
# ── Voices ──
def list_voices(self) -> List[Dict[str, str]]:
"""kind="registered" entries get a "Delete voice" button. Read-only: never start the service."""
return [
{"id": "voice_en_male", "name": "Deep Voice (English)", "lang": "en", "kind": "speaker"},
{"id": "voice_zh_female", "name": "Sweet Voice (Chinese)", "lang": "zh", "kind": "speaker"},
]
def register_voice(self, voice_name, audio_samples, reference_text="", sample_rate=24000) -> bool:
"""audio_samples: float32 mono numpy (1~30s) provided by the host (file or in-app recording)."""
self._start_server_if_needed() # explicit user action: starting the service is fine
return True
def remove_voice(self, voice_name: str) -> bool:
self._start_server_if_needed()
return True
def synthesize(
self,
text: str,
voice_id: str = "",
voice: Optional[str] = None,
language: str = "en",
) -> Tuple[np.ndarray, int]:
self._start_server_if_needed() # request entry point self-heal (no-op when ready)
selected = (voice or voice_id or self._config.get("voice") or "voice_en_male")
duration = 0.5
t = np.linspace(0, duration, int(self.sample_rate * duration), endpoint=False)
audio = (0.3 * np.sin(2 * np.pi * 440 * t)).astype(np.float32)
return audio, self.sample_rate
def stream_tts(self, text: str, voice_id="", voice=None, language="en") -> Iterator[np.ndarray]:
"""Optional: yield chunks for earlier playback start; otherwise the host falls back to synthesize."""
yield self.synthesize(text, voice_id, voice, language)[0]2.5 AI Interview Copilot (Assistant)
copilot.py:
from __future__ import annotations
import time
from typing import Any, Dict, Optional
from transbox.plugins.base import BaseAssistantPlugin
class CopilotPlugin(BaseAssistantPlugin):
def handle_action(self, action: str, params: Dict[str, Any]) -> Any:
if action == "ask":
self.run_in_background(self._async_solve, params)
return {"status": "started"}
return None
def _async_solve(self, params: Dict[str, Any]) -> None:
# Retrieve pure source transcripts from host (peer=interviewer, self=user; no machine translation)
history = self.host.get_recent_conversation(max_turns=3) if self.host else []
self.emit_event("start", {"title": "Analyzing question..."})
for chunk in ["Key Points: ", "1. System High Availability; ", "2. Full-duplex streaming."]:
time.sleep(0.3)
self.emit_event("chunk", {"delta": chunk})
self.emit_event("done", {"summary": "Analysis complete"})2.6 End-to-End Simultaneous Interpretation (Interpretation)
my_interpretation.py:
from __future__ import annotations
import numpy as np
from transbox.plugins.base import BaseInterpretationProvider
class MySIProvider(BaseInterpretationProvider):
def start_session(
self,
session_id: str = "peer",
source_lang: str = "zh",
target_lang: str = "en",
voice: str | None = None,
tts_enabled: bool = False,
) -> bool:
self.emit_status("streaming", session_id)
return True
def feed_audio(self, session_id: str, pcm_chunk: np.ndarray) -> None:
self.emit_transcript("Hello", is_final=False, session_id=session_id)
self.emit_translation("Hello", "你好", is_final=False, session_id=session_id)
def stop_session(self, session_id: str = "peer") -> None:
self.emit_status("idle", session_id)
def is_active(self, session_id: str = "peer") -> bool:
return True2.7 Quick Translate Tool with Global Hotkey (Tool)
my_tool.py:
from __future__ import annotations
from typing import Any, Dict, Optional
from transbox.plugins.base import BaseToolPlugin
class QuickTranslateTool(BaseToolPlugin):
def initialize(self, config: Optional[Dict[str, Any]] = None) -> None:
super().initialize(config)
if self.host:
self.host.register_plugin_hotkey(self.id, "run", "Alt+Q", name="Quick Translation")
def action_run(self, **kwargs) -> Dict[str, Any]:
if self.host:
text = "Welcome to TransBox"
translated = self.host.translate(text, source="en", target="zh")
self.host.feed_caption(text=text, translation=translated, source="plugin", tts=True)
self.host.show_toast(f"Translation: {translated}", level="info")
return {"ok": True}
def shutdown(self) -> None:
if self.host:
self.host.unregister_plugin_hotkeys(self.id)
super().shutdown()3. Implementation Checklist for AI Agents
Verify each item before implementing:
- [ ]
mod.jsoncontains a non-empty uniqueid - [ ]
type∈ - [ ]
mtplugins declaretarget_langsinmod.json(including Chinese and English); see Language codes & translatable languages - [ ] For offline local models (
asr/mt/tts), includelocal,offline, orgpuintagsto trigger offline hardware management - [ ]
entrypointpath and class name really exist in the package - [ ] Python class inherits the correct base class and implements all abstract methods
- [ ] If plugin needs i18n, provide
locales/zh-CN.jsonandlocales/en-US.jsonin plugin root; never modify host core source files - [ ] If plugin provides UI, use
ThemeTokensandPluggableWindowBackgroundfor automatic light/dark adaptation; never use emojis or character glyphs as icons, use clean vector SVGs - [ ] Glossary text separator is
=or Tab (not->) - [ ]
window_backdropappears only intheme.json - [ ] UI slot names use the consumed names from the table below (§15, including
copilot_window) - [ ] No undocumented third-party packages; no runtime pip
- [ ] Every
config_schemaitem has bothkeyandlabel - [ ] After dropping into
extensions/, the MOD is visible and enableable in the Workshop list
4. Where the Other Sections Went
| Original section | Now lives in |
|---|---|
§1 Directory Structure · §2 mod.json Field Table · §4 entrypoint Rules | Layout & mod.json |
§3 Supported type Values (incl. §3.1 caption_extractor) | Type overview & selection · Video-site caption extraction |
| §5.1 Glossary · §5.2 Theme · §5.3/§5.4 i18n · §5.5 tool | glossary · theme · i18n · tool |
§6 config_schema (incl. §6.1 QML consumption · §6.2 hot reload · §6.3 settings view) | config_schema · Custom settings view |
| §7.1–7.9 Base classes / data types / Host SPI | Provider signatures & data types · SPI index & cheat sheet |
| §7.10 Local offline model artifacts & external runtime | Model artifacts |
§7.11 Host runtime dependencies requires_runtime | Host runtimes |
| §9 Local hot debugging | Hot debugging |
| §10 In-app one-click publishing & updates | Packaging & size limit · Publish channels |
| §11 Self-contained plugin locales specification | i18n |
| §12 Runtime environment & hard rules | Runtime environment & red lines |
| §13 Official example index | Example library |
| §15 UI Slots (summary) | UI slots · QML slots & HostContext |
More development topics (local-service contract, dependency gates, threading contract, release process…) are listed in the Plugin Development Docs.
The core team reserves the right to evolve this specification. Please open a GitHub issue for questions.
