Skip to content

插件规范(代码资产 · 导航) ​

本页只保留可直接复制的代码资产: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:

text
你是一个专业的 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 作为类型桩:

python
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): pass


2. 最小可运行模板 ​

2.1 自定义 MT(可直接改名使用) ​

mod.json:

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:

python
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 中间件 ​

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

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:

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

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:

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

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:

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

python
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 True

2.7 全局快捷键与划词翻译工具 (Tool) ​

my_tool.py:

python
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 toolglossary · theme · i18n · tool
§6 config_schema(含 §6.1 QML 消费 · §6.2 热响应 · §6.3 设置页)config_schema · 自定义设置页
§7.1–7.9 基类签名 / 数据类型 / Host SPIProvider 签名与数据类型 · SPI 索引与速查表
§7.10 本地离线模型工件与外部运行时模型工件
§7.11 宿主运行库依赖 requires_runtime宿主运行库
§9 本地热调试热调试
§10 创意工坊一键发布与增量更新打包与体积上限 · 发布渠道
§11 插件自包含国际化规范i18n 语言包
§12 运行环境与红线运行环境与安全红线
§13 官方示例索引示例库
§15 UI 插槽(摘要)UI 插槽 · QML 界面插槽与 HostContext

更多开发主题(本地服务契约、依赖门禁、线程契约、发布流程…)见 插件开发文档总览。


核心团队保留规范演进权。问题请开 GitHub Issue。

TransBox - 赋能全人类无界限跨语言交流