• wbc

    我也是后知后觉发现 Paseo 的好处,已经有不少文章介绍过如何安装配对等,下面跳过 Paseo 的安装和基础设置,主要记录一下Relay和语音输入的配置,希望对大家有帮助:

    Relay

    Paseo 支持在手机上访问电脑端的 Paseo daemon,实现随时随地无缝vibe coding。但是 paseo.sh 的服务器在海外,利用 Cloudflare DO 进行中继,所以国内使用时,经常连接不上,或是断连。

    所以,自建一个国内的 relay 能很好的解决这些问题,这里我使用了 paseo-relay 这个项目来搭建自己的relay服务器,详细步骤参考项目的说明。

    在服务器上通过 Docker或配置成系统服务的方式运行在 8411 端口上,再通过 Caddy 自签域名证书支持HTTPS访问,假定我们这里配置好的中继服务器是 relay.abc.com, 通过访问 https://relay.abc.com/health 可以得到下面的返回,则说明服务正常运行了:```
    {"status":"ok","version":"v0.5.0"}
    ``下一步修改本地的 Paseo 配置,无论是通过npm安装的,或是使用 Paseo软件启动的,配置文件目录默认在:``

    MacOS

    ~/.paseo/
    ``打开配置文件目录下的 config.json, 找到 daemon 下的 relay 进行修改或添加:``

    注意域名后面带端口: relay.abc.com:443

    {
    "daemon": {
    ...
    "relay": {
    "enabled": true,
    "endpoint": "relay.abc.com:443",
    "publicEndpoint": "relay.abc.com:443",
    "useTls": true
    }
    ...
    },
    "app": {
    "baseUrl": "https://relay.abc.com"
    }
    }
    ```保存后,重启 Paseo (命令或在Paseo软件中找到指定的主机 "概览“-> “重启 Daemon”),再次生成配对设备二维码时,已经是新的 Relay 服务器地址了,在“连接”里面看到的也是 Relay服务器。

    语音输入

    Paseo 已经支持了 Voice,支持两种语音识别文字方案:

    - OpenAI格式的API方式

    - 本地的 STT/TTS 模型

    本地语音模型

    在未进行配置时,在页面上点击麦克风图标,Paseo 将自动下载模型,模型下载完成后即可使用语音输入了。默认从 github 上下载,如果网速慢失败,则可以尝试下:```

    通过 gh-proxy, 注意先切到paseo 模型目录下, 建议使用 parakeet-tdt-0.6b-v3

    cd ~/.paseo/models/local-speech
    wget -c "https://v4.gh-proxy.org/https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8.tar.bz2"
    tar -xJf sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8.tar.bz2

    wget -c "https://v4.gh-proxy.org/https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8.tar.bz2"

    tar -xJf sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8.tar.bz2

    wget -c "https://v4.gh-proxy.org/https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-en-v0_19.tar.bz2"
    tar -xJf kokoro-en-v0_19.tar.bz2
    ``如果在MacOS等环境上,本地的语音模型是比较合适的,截止到 26年8月底的版本,Paseo本地使用了 STT: sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8 和 TTS: kokoro-en-v0_19` 两个模型。

    不幸的是, STT: sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8 只支持英语和欧洲的几种语言,不支持中文。由于当下版本中,固定了 nemo 方式的语音识别,尚不支持 SenseVoice 格式的onnx,有几个 PR: 1634 #2044 #3689 官方合并后,我们可以使用上这些模型了:

    - sense-voice-zh-en-ja-ko-yue-int8-2025-09-09

    - paraformer-zh-2024-03-09-int8

    文本转语音API

    除了本地的模型, Voice 文档中更建议使用 API的方式做语音识别,如果使用 OpenAI 官方的只要按照文档配置即可。

    本地自建的 FunASR 支持 OpenAI Compatible API,所以比较简单,只要配置好 baseUrl 和 model: paraformer 即可:```
    {
    "features": {
    "dictation": {
    "stt": {
    "provider": "openai"
    }
    },
    "voiceMode": {
    "stt": {
    "provider": "openai",
    "model": "paraformer"
    }
    }
    },
    "providers": {
    "openai": {
    "stt": {
    "apiKey": "...",
    "baseUrl": "http://localhost:8000/v1"
    }
    }
    }
    }
    ```这里,千问AI平台也提供了语音识别的服务,调用按音频时长计费价格非常低,只有 0.00022/秒。

    虽然千问提到了兼容 OpenAI 接口,但实际上 qwen3-asr-flash 这些模型请求的 endpoint是 /v1/chat/completions,而不是OpenAI 的 /v1/audio/transcriptions,而且发的请求体格式也不同。

    这里通过 Cloudflare Worker 做了一个格式转换,达到了相同的效果,也可以类似的用其他云的edge runner 来实现。假设配置好后的Cloudflare Worker域名是 audio.abc.com,Paseo config.json 参考下面配置,并重启 Paseo Daemon后,即可在 Paseo 软件上使用语音输入了。```

    修改 ~/.paseo/config.json

    {
    "features": {
    "dictation": {
    "stt": {
    "provider": "openai",
    "model": "qwen3-asr-flash"
    }
    },
    "voiceMode": {
    "stt": {
    "provider": "openai",
    "model": "qwen3-asr-flash"
    }
    }
    },
    "providers": {
    "openai": {
    "stt": {
    "apiKey": "千问AI平台的 api key",
    "baseUrl": "https://audio.abc.com/v1"
    }
    }
    }
    }
    ``Cloudflare Worker 代码``
    export default {
    async fetch(request, env, ctx) {
    // 1. 处理 CORS 预检请求 (Paseo 在浏览器环境下可能会发 OPTIONS)
    if (request.method === "OPTIONS") {
    return new Response(null, {
    headers: {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
    });
    }

    const url = new URL(request.url);

    // 2. 专门拦截 STT 语音转写请求
    if (request.method === "POST" && url.pathname.endsWith("/v1/audio/transcriptions")) {
    try {
    const formData = await request.formData();
    const file = formData.get("file");

    if (!file) {
    return new Response(JSON.stringify({ error: "No file provided" }), { status: 400 });
    }

    // 提取音频并转为 Base64
    const arrayBuffer = await file.arrayBuffer();
    const base64Audio = arrayBufferToBase64(arrayBuffer);
    const mimeType = file.type || "audio/webm";
    const audioDataUri = data:${mimeType};base64,${base64Audio};

    // 组装千问多模态对话请求
    const payload = {
    model: "qwen3-asr-flash",
    messages: [
    {
    role: "user",
    content: [
    {
    type: "input_audio",
    input_audio: {
    data: audioDataUri,
    },
    },
    ],
    },
    ],
    stream: false,
    };

    // 发送给阿里云
    const aliyunResponse = await fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    "Authorization": request.headers.get("Authorization"),
    },
    body: JSON.stringify(payload),
    });

    if (!aliyunResponse.ok) {
    const errText = await aliyunResponse.text();
    return new Response(errText, { status: aliyunResponse.status, headers: { "Content-Type": "application/json" } });
    }

    const data = await aliyunResponse.json();
    const messageContent = data.choices?.[0]?.message?.content;
    let transcribedText = "";

    // 解析千问的返回体提取文本
    if (typeof messageContent === "string") {
    transcribedText = messageContent;
    } else if (Array.isArray(messageContent)) {
    for (const item of messageContent) {
    if (item.type === "text") {
    transcribedText += item.text || "";
    }
    }
    }

    // 返回标准 OpenAI STT 格式给 Paseo
    return new Response(JSON.stringify({ text: transcribedText.trim() }), {
    headers: {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*",
    },
    });
    } catch (err) {
    return new Response(JSON.stringify({ error: err.message }), { status: 500 });
    }
    }

    // 3. 对其他请求(例如 Paseo 的大模型对话请求 /v1/chat/completions),直接代理转发
    const targetUrl = new URL("https://dashscope.aliyuncs.com" + url.pathname + url.search);
    const proxyRequest = new Request(targetUrl, request);
    // 剔除可能会导致云端 WAF 拦截的头
    proxyRequest.headers.delete("Host");

    const response = await fetch(proxyRequest);
    const newResponse = new Response(response.body, response);
    newResponse.headers.set("Access-Control-Allow-Origin", "*");
    return newResponse;
    },
    };

    // 辅助函数:由于音频文件较大,分块转 Base64 避免内存溢出
    function arrayBufferToBase64(buffer) {
    let binary = "";
    const bytes = new Uint8Array(buffer);
    const chunkSize = 0x8000; // 32KB 分块
    for (let i = 0; i < bytes.length; i += chunkSize) {
    const chunk = bytes.subarray(i, i + chunkSize);
    binary += String.fromCharCode.apply(null, chunk);
    }
    return btoa(binary);
    }

    主楼
  • neroZac

    分享下我的经历, 感觉其实语音输入, 直接用搜狗或者讯飞, 或者任意一家输入法的语音输入就好了. 文本转语音我目前用的是minmax的api, 有时候回答内容长就听

    #1
  • ihalo

    我都想关闭这个语音输入,经常误触,主要默认不识别中文

    #2
  • chongwen

    我觉得CodexHost可能也适合,简单说是在codex desktop的页面上能用其他的harness。

    也支持远程(windows、mac、linux互相连接,手机不行),

    然后对于语言输入,这点很爽,直接用codex自己的语音能力。

    图片

    本帖使用社区开源推广,符合推广要求。我申明并遵循社区要求的以下内容:

    我的帖子已经打上 开源推广 标签: 是
    我的开源项目完整开源,无未开源部分: 是
    我的开源项目已链接认可 LINUX DO 社区: 是
    我帖子内的项目介绍,AI生成、润色内容部分已截图发出: 是
    以上选择我承诺是永久有效的,接受社区和佬友监督: 是

    [image]
    我最近非常喜欢用 Pi,但 Pi 没有官方 GUI,只…

    #3
  • neroZac

    确实. 可惜手机不能远程, 用paseo最主要就是用他方便的远程vibe能力

    #4
  • Cland

    中文识别还是硬伤,要有数据支撑才能把中文场景做好,有的场景感觉不如直接调用第三方输入法的语音来得实在。

    #5
  • HeriX

    他的语音识别是可以关掉的 改成disabled, 不如直接用现成的输入法的语音功能, 比如豆包,微信啥的

    这样可以不需要下载占空间的语音模型包

    #6

发表回复

登录后回复