Skip to content

assistant — AI assistant (Copilot) ​

Interview / defence / meeting copilot: hotkey help, context packing, multimodal Q&A, capture-safe floating window. Multiple instances allowed.


Declaration ​

json
{
  "id": "community.assistant.interview_copilot",
  "name": "AI 面试答题助手",
  "version": "1.0.0",
  "target_app_version": "0.2",
  "type": "assistant",
  "entrypoint": "copilot.py:InterviewCopilotPlugin",
  "icon": "icon.svg",
  "has_settings_tab": true,
  "tab_id": "copilot",
  "tab_title": "AI 面试助手",
  "ui_extensions": {
    "copilot_window": "Window.qml",
    "style_bar_actions": "Action.qml",
    "settings_view": "SettingsView.qml"
  },
  "config_schema": [
    { "key": "provider_preset", "label": "服务商预设", "type": "select",
      "choices": ["小米 Token Plan", "DeepSeek", "Ollama 本地", "自定义"],
      "presets": {
        "Ollama 本地": { "base_url": "http://127.0.0.1:11434/v1", "model": "qwen2.5" }
      } },
    { "key": "api_key", "label": "API Key", "type": "password", "is_secret": true }
  ]
}

Shape ​

python
from transbox.plugins.base import BaseAssistantPlugin


class InterviewCopilot(BaseAssistantPlugin):
    def action_ask(self, prompt="", image_data="", include_history=True, turns=5, **kwargs):
        # 1. Fetch speaker-labelled structured history (ASR source text only, no machine translation, to avoid distortion)
        recent_turns = self.host.get_recent_conversation(turns)
        # 2. Slow network requests go to a background daemon thread; never block the Qt UI
        self.run_in_background(self._do_stream, prompt, image_data, recent_turns)

    def _do_stream(self, prompt, image_data, history):
        # 3. Push streaming typewriter events to QML token by token
        self.emit_event("stream_chunk", {"delta": "要点…", "done": False})
        self.emit_event("stream_done", {"full_text": "完整答案…"})

    def action_cancel(self, **kwargs):
        ...
  • action_ask is triggered from QML through bridge.invokePluginActionAsync(pluginId, "ask", {...}), or by a hotkey.
  • get_recent_conversation() returns {id, speaker, speaker_label, transcript, translation, time}, where speaker is peer / self. Feed only transcript (the source text) to the model — machine translation adds a second layer of distortion.
  • Use self.emit_event() to push intermediate states to QML (see Event bus).

Mounting floating windows ​

json
"ui_extensions": {
  "copilot_window": "Window.qml",
  "style_bar_actions": "Action.qml"
}
  • copilot_window: a standalone top-level frosted-glass interactive floating window (mounted on demand by the host main.qml). A mutually exclusive slot — only one assistant can use it at a time.
  • style_bar_actions: insert custom action buttons into the caption bar's control row.

When you need several independent floating windows (not just the single copilot one), use Plugin windows SPI: host.create_plugin_window(...) supports multiple instances, positioning, click-through and capture exclusion.


Capture / screen-share exclusion (a hard requirement for interviews) ​

In your QML window:

qml
Component.onCompleted: bridge.setWindowExcludeFromCapture(this, true)

This uses the native Windows SetWindowDisplayAffinity(0x00000011) — Tencent Meeting / Zoom / Teams and similar screen shares see the window as completely invisible. Returns False on non-Windows platforms.


Design aesthetics rules ​

  • All QML must adapt to theme switching automatically via ThemeTokens and PluggableWindowBackground.
  • Never use glyphs or emoji as icons — pure vector SVG only.

Reference implementation ​

extensions/community_ai_interview_copilot/.


TransBox — empowering borderless cross-language communication for everyone