插件规范(代码资产 · 导航)
本页只保留可直接复制的代码资产:Prompt-and-Go 提示词模板、独立开发 SDK 存根、7 个完整可运行模板、AI 实现检查清单。
所有散文型契约(目录结构 /
mod.json字段表 /type选型 /entrypoint/config_schema/ 基类签名 / Host SPI / 模型工件 / 运行库依赖 / UI 插槽 / 运行环境与红线 …)已按主题拆分为知识卡片, 请从 插件开发文档总览 进入——一页一个主题,每件事只写一遍。契约的单一事实来源是客户端源码(
src/transbox/plugins/、src/transbox/ui/bridge/): 文档与代码冲突时以代码为准。本页与docs/plugin-dev/卡片同源同结构。
0. 直接丢给 AI:系统提示词模板 (Prompt-and-Go)
配套读物:把本页 §1(SDK 存根)与 插件开发文档 中对应类型的那张卡片一起贴给 AI, 生成结果即可直接落在插件目录里。
如果你希望让大语言模型(如 ChatGPT、Claude、DeepSeek 等 AI 编程助手)在完全看不到 TransBox 软件源代码的情况下,为你独立开发并生成一个完整、合规、立即可用的插件,请直接将以下提示词模板与本文档内容复制并发送给 AI:
你是一个专业的 TransBox 客户端插件开发工程师。请严格遵循《TransBox 插件开发契约与规范》,在看不到主程序源代码的前提下,为我开发一个功能合规的 TransBox 插件。
【开发目标与需求】:
<在这里描述你的具体功能需求,例如:接入某云端 ASR/MT/TTS、制作一个面试提词副驾、编写专有名词词库等>
【必须遵守的核心铁律】:
1. 目录结构合规:输出独立文件夹,根目录必须包含合法的 mod.json,且 id 全局唯一(格式:作者.类型.名称,严禁使用 builtin.*);
2. 契约继承完整:Python 插件必须从 transbox.plugins.base 继承对应的基类(BaseASRProvider / BaseMTEngineProvider / BaseTTSProvider / BaseAssistantPlugin / BaseInterpretationProvider / BasePipelineMiddleware / BaseToolPlugin),并完整实现全部抽象方法,入参与返回值类型严格对齐规范;
3. 线程安全与异步:任何涉及网络请求、大模型推理或耗时计算的操作,必须使用 self.run_in_background() 异步执行并通过 self.emit_event() 派发流式事件,严禁阻塞 Qt 主线程;
4. 配置与动态表单:简单字段用 config_schema;需要「去获取 Key」「测试连接」等定制 UX 时必须自带 ui_extensions.settings_view QML。initialize()/update_config() 通过 self._config.get() 读取,严禁硬编码私钥或个人路径;
5. 自包含多语言:如包含 UI 文本或配置选项,在插件目录下的 locales/ 文件夹中提供自包含的 zh-CN.json 和 en-US.json,词条前缀统一为 plugin.<plugin_id>.*,严禁修改主程序核心词库;
6. 视觉规范:若包含 QML 界面,统一使用 ThemeTokens 与 PluggableWindowBackground 自动适配主题;严禁使用 Emoji 或丑陋文本字符充当图标,必须使用矢量 SVG。
请按标准结构输出:
1. 插件完整文件目录树;
2. mod.json 完整清单内容;
3. Python / QML / JSON 源码(完整无缺,禁止用省略号带过核心逻辑);
4. 本地测试与一键发布指引。1. 独立开发环境存根 (SDK Stub for Standalone Dev)
面向 AI 代理与外部开发者的关键约定: 当插件运行在 TransBox 客户端内时,主程序会自动提供
transbox.plugins.base运行时包; 如果你在完全没有主程序源码的独立开发环境(如独立的 Python 虚拟环境、CI 构建脚本或单元测试)中编写代码,建议在插件入口顶部编写以下兼容导入,或在开发目录中放置transbox_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:
# ── 独立开发/单元测试/AI 编写时的轻量自包含存根 ──
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. 最小可运行模板
2.1 自定义 MT(可直接改名使用)
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)启用后:设置 → 机器翻译 → 引擎下拉中选中该引擎。
2.2 中间件
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 自定义语音识别 ASR(支持整句与流式)
mod.json:
{
"id": "myteam.asr.demo",
"name": "示范 ASR 识别引擎",
"version": "1.0.0",
"type": "asr",
"entrypoint": "my_asr.py:MyASRProvider",
"config_schema": [
{
"key": "api_url",
"label": "识别服务地址",
"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:
# 声明能力:是否自带 VAD、是否支持长连接流式识别
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:
# 整句识别:audio 为 float32 单声道 16kHz numpy 数组(数值范围 [-1.0, 1.0])
# 实际开发中可将其转为 wav 字节并通过 requests 抛给本地或云端服务
return Transcript(text="[ASR识别示范] 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:
# 实时喂入 PCM 音频切片
return ""
def stream_events(self, stream_id: str) -> Iterator[Tuple[str, bool]]:
# 迭代返回流式打字机事件:(text, is_final)
yield ("Hello", False)
yield ("Hello World", True)
def stream_close(self, stream_id: str) -> str:
return "Hello World"2.4 自定义语音合成 TTS
mod.json:
{
"id": "myteam.tts.demo",
"name": "示范 TTS 语音合成",
"version": "1.0.0",
"type": "tts",
"entrypoint": "my_tts.py:MyTTSProvider",
"capabilities": {
"voices": true,
"voice_clone": true,
"voice_remove": true,
"speed": false
},
"config_schema": [
{
"key": "voice_speed",
"label": "语速调节",
"type": "number",
"default": 1.0
}
]
}
capabilities.voice_clone置true后,设置页会出现「注册克隆音色」面板:用户可以在软件内直接录制 1~30 秒参考音频(也可以选已有文件),宿主负责麦克风采集/写 WAV/读回 float32 采样,插件只需实现register_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
from transbox.plugins.manifest import ModManifest
class MyTTSProvider(BaseTTSProvider):
supports_voice_clone = True # 与 mod.json capabilities 等价,二者取并集
supports_speed = False # False → 宿主统一做保音高变速,你什么都不用做
@property
def sample_rate(self) -> int:
return 24000
# ── 生命周期(惰性:initialize 不得启动服务,见 [本地服务型引擎强制契约](../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):
"""宿主在“真正要用”时调用:这时才拉起本地服务(云端 API 插件可实现为空)。"""
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", "模型未就绪,请在设置页下载"
return True, "", "" # 服务没起也算可用
# ── 音色 ──
def list_voices(self) -> List[Dict[str, str]]:
"""kind=registered 的音色才会显示「删除音色」。只读:服务未起就返回缓存/空表。"""
return [
{"id": "voice_zh_female", "name": "甜美主播 (中文)", "lang": "zh", "kind": "speaker"},
{"id": "voice_en_male", "name": "Deep Voice (English)", "lang": "en", "kind": "speaker"},
]
def register_voice(self, voice_name, audio_samples, reference_text="", sample_rate=24000) -> bool:
"""audio_samples: float32 单声道 numpy(1~30s),由宿主提供(选文件或软件内录制)。"""
self._start_server_if_needed() # 显式用户操作:可以拉起服务
... # 转码/上传/落盘,成功返回 True
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 = "zh",
) -> Tuple[np.ndarray, int]:
self._start_server_if_needed() # 请求入口兜底(已就绪时零开销)
# 用户选中的音色写在插件配置的 voice 键(分语种时 voice_en / voice_other)
selected = (voice or voice_id or self._config.get("voice") or "voice_zh_female")
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="zh") -> Iterator[np.ndarray]:
"""可选:按块 yield,起播更早。未实现时宿主自动退化为整段 synthesize。"""
yield self.synthesize(text, voice_id, voice, language)[0]2.5 AI 面试答题副驾 (Assistant + Copilot)
mod.json:
{
"id": "myteam.assistant.copilot",
"name": "AI 面试辅导副驾",
"version": "1.0.0",
"type": "assistant",
"entrypoint": "copilot.py:CopilotPlugin",
"ui_extensions": {
"copilot_window": "CopilotWindow.qml"
},
"config_schema": [
{
"key": "api_key",
"label": "大模型 API Key",
"type": "password",
"default": ""
}
],
"permissions": ["net", "window"]
}copilot.py:
from __future__ import annotations
import time
from typing import Any, Dict, Optional
from transbox.plugins.base import BaseAssistantPlugin
from transbox.plugins.manifest import ModManifest
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:
user_prompt = params.get("prompt", "")
# 1. 从宿主安全获取最近纯原文对话历史(peer=面试官/对方,self=我方;绝不带机翻)
history = self.host.get_recent_conversation(max_turns=3) if self.host else []
context_str = "\n".join([f"{h.get('speaker')}: {h.get('transcript')}" for h in history])
# 2. 模拟大模型流式思考与回答,通过 emit_event 实时推送到前端 QML
self.emit_event("start", {"title": "正在分析问题..."})
for chunk in ["核心考点:", "1. 架构高可用;", "2. 全双工流式;", "3. 内存与并发安全性。"]:
time.sleep(0.3)
self.emit_event("chunk", {"delta": chunk})
self.emit_event("done", {"summary": "分析完毕"})2.6 端到端全双工实时同传 (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:
# 建立与远端同传长连接 WebSocket
self.emit_status("streaming", session_id)
return True
def feed_audio(self, session_id: str, pcm_chunk: np.ndarray) -> None:
# 收到实时 16kHz PCM 音频切片,推入 WebSocket
# 当服务端返回数据时,调用派发事件(例如识别中间态与定稿翻译):
self.emit_transcript("你好", 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 全局快捷键与划词翻译工具 (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)
# 注册全局快捷键 Alt+Q 绑定 action_run
if self.host:
self.host.register_plugin_hotkey(self.id, "run", "Alt+Q", name="快速划词翻译")
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"划词翻译完成: {translated}", level="info")
return {"ok": True}
def shutdown(self) -> None:
if self.host:
self.host.unregister_plugin_hotkeys(self.id)
super().shutdown()3. 给 AI 代理的实现检查清单
实现前逐项核对:
- [ ]
mod.json含非空唯一id - [ ]
type∈ - [ ]
mt插件在mod.json声明target_langs(含中英),见 语言代码与可翻译语种 - [ ]
entrypoint路径与类名在包内真实存在 - [ ] Python 类继承正确基类,实现全部抽象方法
- [ ] 若插件具备多语言需求,在插件根目录下提供
locales/zh-CN.json和locales/en-US.json,严禁修改宿主源码 - [ ] 若插件具备 UI 界面,统一使用
ThemeTokens和PluggableWindowBackground自动适配暗/明色主题,严禁使用 Emoji 或丑陋文本符号充当图标,必须使用矢量 SVG - [ ] glossary 仅为四字段
terms.json对象数组(勿用 txt/tsv/扁平字典) - [ ]
window_backdrop只出现在theme.json - [ ] UI 插槽名使用下表已消费名称(§15,含
copilot_window) - [ ] 不依赖未文档化的第三方包;不在运行时 pip
- [ ]
config_schema每项都有key与label - [ ] 本地放入
extensions/后可在工坊列表看到并启用
4. 其它章节去哪
| 原章节 | 现在的位置 |
|---|---|
§1 目录结构 · §2 mod.json 字段表 · §4 entrypoint | 目录结构与 mod.json |
§3 支持的 type(含 §3.1 caption_extractor) | 类型总览与选型 · 视频站字幕提取 |
| §5.1 Glossary · §5.2 Theme · §5.3/§5.4 i18n · §5.5 tool | glossary · theme · i18n · tool |
§6 config_schema(含 §6.1 QML 消费 · §6.2 热响应 · §6.3 设置页) | config_schema · 自定义设置页 |
| §7.1–7.9 基类签名 / 数据类型 / Host SPI | Provider 签名与数据类型 · SPI 索引与速查表 |
| §7.10 本地离线模型工件与外部运行时 | 模型工件 |
§7.11 宿主运行库依赖 requires_runtime | 宿主运行库 |
| §9 本地热调试 | 热调试 |
| §10 创意工坊一键发布与增量更新 | 打包与体积上限 · 发布渠道 |
| §11 插件自包含国际化规范 | i18n 语言包 |
| §12 运行环境与红线 | 运行环境与安全红线 |
| §13 官方示例索引 | 示例库 |
| §15 UI 插槽(摘要) | UI 插槽 · QML 界面插槽与 HostContext |
更多开发主题(本地服务契约、依赖门禁、线程契约、发布流程…)见 插件开发文档总览。
核心团队保留规范演进权。问题请开 GitHub Issue。
