Communitygithub.com

V4TIME/V4SKILL

在Termux中构建集成NaraRouter的Flask AI应用。

V4SKILL 是什么?

该技能指导用户在Android设备上的Termux环境中构建并运行本地Web应用程序,特别集成AI API(尤其是NaraRouter),并可选用Hermes Agent作为推理后端。它涵盖了从快速启动Flask应用到集成多种AI API(如NaraRouter和NVIDIA NIM)的完整流程,包括API密钥测试、模型选择、以及任务分派引擎的构建。技能还强调了在移动网络环境下的最佳实践,例如推荐使用Python标准库`urllib`代替`requests`以避免安装超时,并提供了详细的调试步骤,涵盖端口冲突、API认证失败、以及前端JavaScript错误排查。此外,它包含了用户特定的设计偏好:极简、专业、避免AI生成风格(如无动画、无渐变色、无表情符号装饰),并支持通过Web Speech API实现语音模式。该技能适用于Claude Code、Codex等AI Agent平台,帮助开发者在移动设备上高效创建AI驱动的本地Web应用。

兼容平台✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/V4TIME/V4SKILL/tree/HEAD/termux-flask

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

在 Termux 中构建本地 AI Web 应用

在运行 Termux 的 Android 设备上构建并运行本地 Web 应用程序,集成 AI API(尤其是 NaraRouter),并可选地使用 Hermes Agent 作为推理后端。

何时使用

  • 用户希望在手机上运行一个本地 AI 助手/聊天界面
  • 在 Termux 中用 Python/Flask 构建 Web 应用
  • 通过描述驱动路由集成多个 AI API
  • 通过浏览器 Web Speech API 添加语音模式
  • 跨聊天会话管理会话和记忆
  • 用户粘贴了开发工具(二进制发布版、Rust/cargo、npm/npx、pip)的安装说明,并指示安装和使用——首先评估在手机/Termux 环境中的可行性,然后在获得许可后安装

架构模式

浏览器 (Android 上的 Chrome) → Flask 后端 (Termux) → AI API
                                 ↓
                             Hermes Agent (可选,作为大脑)

关键原则: Web 应用是接口层。Hermes Agent(如果使用)是大脑。Flask 后端桥接它们。

快速开始

# 在 Termux 中
mkdir ~/sayip && cd ~/sayip
mkdir -p templates static/css static/js storage/sessions
# app.py — 最小 Flask 启动器
from flask import Flask, render_template, request, jsonify
from pathlib import Path
import json, time, uuid, os

app = Flask(__name__, template_folder="templates")
HERMES_HOME = Path(os.path.expanduser("~/.hermes"))
SAYIP_STORAGE = Path(os.path.expanduser("~/sayip/storage"))

@app.route("/")
def index():
    return render_template("chat.html")

@app.route("/api/chat", methods=["POST"])
def chat():
    data = request.get_json()
    # ...处理消息,调用API,返回响应

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)
# 运行
python3 app.py
# 在 Chrome 中打开: http://127.0.0.1:5000

API 集成模式

NaraRouter (主要推荐)

NaraRouter 是一个统一的 AI 网关,提供 53+ 个模型,提供免费套餐。

密钥格式: sk-nry-... (以 sk-nry- 开头)

端点 (均在同一个基础 URL 下,使用 Bearer 密钥):

端点类型用途
https://router.bynara.id/v1/chat/completionsOpenAI 兼容聊天,通用任务
https://router.bynara.id/v1/messagesAnthropic 兼容Anthropic 格式请求
https://router.bynara.id/v1/responsesOpenAI Responses有状态响应 API
https://router.bynara.id/v1/embeddingsOpenAI Embeddings向量嵌入
https://router.bynara.id/v1/modelsLIST获取您套餐中可用的模型
https://api-images.bynara.id/v1/images/generations图像生成文生图
https://api-images.bynara.id/v1/images/edits图像编辑图像编辑

使用前测试密钥:

curl -s -X POST https://router.bynara.id/v1/chat/completions \
  -H "Authorization: Bearer sk-nry-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"agnes-2.5-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":10}'

已知可用的免费套餐模型: agnes-2.5-flash, agnes-2.0-flash

检查您套餐中的模型:

curl -s https://router.bynara.id/v1/models \
  -H "Authorization: Bearer sk-nry-YOUR_KEY" | python3 -c "import sys,json; [print(m['id']) for m in json.load(sys.stdin).get('data',[])]"

重要提示: 并非所有 53 个模型都在每个套餐中可用。deepseek-v4-pro-0813-bynara 需要积分。deepseek-v4-flash-free 可能未被包含。配置前务必进行测试。

NVIDIA NIM API (integrate.api.nvidia.com/v1)

NVIDIA NIM 通过一个兼容 OpenAI 的端点提供 82+ 个模型。 基础 URL: https://integrate.api.nvidia.com/v1 端点: https://integrate.api.nvidia.com/v1/chat/completions 密钥格式: nvapi- (例如 nvapi-_qJeGmEP...)

使用前测试密钥:

curl -s -X POST https://integrate.api.nvidia.com/v1/chat/completions \
  -H "Authorization: Bearer nvapi-..." \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"model":"nvidia/nemotron-3.5-lightning-30b-a3b","messages":[{"role":"user","content":"hi"}],"max_tokens":10,"stream":true}'

已知可用的免费套餐模型 (已在 Termux 移动网络上测试):

模型 ID密钥状态备注
nvidia/nemotron-3.5-lightning-30b-a3bnvapi-...可工作快速(简单任务约 5-7 秒),推理能力强,支持思考模式。在移动设备上最可靠。
gpt-oss-20bnvapi-...可工作兼容 OpenAI
deepseek-ai/deepseek-v4-pro-0813nvapi-...超时风险在桌面上可工作,在移动 Termux 网络上经常超时
moonshotai/kimi-k3nvapi-...超时风险支持视觉功能,但在移动网络上会超时

密钥不对称警告: 并非所有模型都在所有密钥上可用。密钥 1 (nvapi-_qJeGmEP...) 具有更广泛的访问权限。密钥 2 (nvapi-WDb7Rr...) 限制更多——某些模型会返回 404。在配置前,务必使用特定密钥测试模型。

失效密钥的迹象:

  • 401 Unauthorized — 密钥无效/已过期
  • 404 Not Found 于某个模型 — 该模型在此账户/密钥上不可用
  • 连接超时 (30秒+) — API 从该网络无法访问/过慢
  • Teamorouter sk-teamo-... 密钥 → 在 OpenAI 端点 (https://api.teamorouter.com/v1) 上返回 insufficient_balance;在 Anthropic 端点 (https://api.teamorouter.com) 上模型不受支持。已失效。提供 deepseek-v4-pro-free 模型但因余额不足无法使用。

详细信息请参见 references/nvidia-api.md:模型特定的 Python 模式(Nemotron 思考模式、DeepSeek 非流式、Kimi 视觉多模态)、流式 reasoning_content 提取、urllib 的基于线程的硬超时模式,以及视觉负载格式。

API 调度引擎

当配置了多个 API 时,构建一个调度器:

  1. 对任务进行分类(数学、编码、科学、通用)
  2. 根据描述文本选择最佳 API
  3. 调用它,返回结果
  4. 如果第一个失败,则回退到其他 API

集成细节请参见 references/nararouter-api.md。

使用标准库 urllib,而非 requests

在 Termux 中,pip install requests 可能会因为下载大包而超时。请改用 Python 的标准库 urllib——它始终可用,并且能很好地处理 JSON API:

import urllib.request, json

def call_api(url, payload, api_key):
    data = json.dumps(payload).encode()
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read())

设计偏好 (用户特定)

极简、专业、非 AI 生成的外观:

  • 深色主题,紧凑间距,无渐变,无装饰性阴影
  • 适当使用等宽字体(代码、API 密钥、时间戳)
  • 聊天气泡使用纯矩形——除非有功能需要,否则不要用圆角
  • 侧边栏 = 纯列表,除非有意义否则不使用图标
  • 搜索参考实现,而不是从头生成设计
  • 有疑问时,删减功能而不是增加功能

具体反模式——这些会让 Web 应用看起来像 AI 生成的,请删除:

  • 每个标题/磁贴/按钮中的表情符号(🤖、⚙、🎭、🔌、🧠、💬、🎤)——移除。仅使用纯文本标签。
  • 悬停时的强调发光/盒子阴影 (box-shadow: 0 0 12px var(--accent-glow)) ——移除。仅使用纯色。
  • 弹跳/脉冲/滑入的关键帧动画 (@keyframes bounce、@keyframes pulse、@keyframes slideDown) ——移除。无动效设计。
  • 带有图标的启动画面欢迎卡片 (包含 welcome-icon、welcome-hints 网格的 .welcome-screen) ——替换为输入区域中的单行提示占位符。
  • Logo 旁的装饰性品牌图标 (带有表情符号的 .brand-icon) ——移除。
  • 装饰标题的丝带、徽章、状态点 (.health-badge、.health-dot) ——移除或使其纯粹功能性。
  • 渐变背景、玻璃态效果、半透明叠加层——绝不使用。

经验法则: 在浏览器中打开页面,并问自己“一个人类开发者会为实用工具手写这种 CSS 吗?”如果答案是否定的(因为它有动效、发光或表情符号装饰),则删除它。一个正常的工具看起来像终端、笔记应用或极简聊天——而不是像着陆页。

语音模式:

  • 使用 Web Speech API(SpeechRecognition + SpeechSynthesis)——免费,内置于浏览器中
  • 使用切换按钮在文本/语音模式之间切换
  • 在语音模式下:麦克风持续监听,语音→文本→发送,响应→朗读
  • 语音质量受限于 Android 上 Chrome 提供的功能——不要承诺“Ultron 声音”

工作流偏好 (用户特定)

安装前征求许可: 在运行 pip install 或 apt install 之前,先询问用户。这是不可商量的。

持续显示进度: 如果任务需要几分钟以上,请显示中间结果。不要长时间不输出。如果卡在某些事情上,请说明并告知正在尝试的方法。

无需安全说教: 用户了解 API 密钥的风险。除非明确要求,否则不要就密钥安全性、暴露或最佳实践进行说教。他们在相关时提供了自己的解释。

偏好可行的解决方案而非完美的架构: 先让某些东西跑起来。NaraRouter 密钥测试显示了这一点——测试密钥,找到可行的模型,配置它。不要过度规划。

存储是易失的——尽早备份到 GitHub: 在 Termux/Android 上,应用存储 (~/sayip/storage/) 可能在服务器重启、Termux 重启或设备重启时被清除。设置好 API 密钥和配置后,立即通过 SSH 将项目推送到 GitHub 仓库,以便恢复配置。在会话中,不要假设 storage/ 能在重启后幸存——在任何重启后重新检查它。通常需要使用 SSH-over-port-443 技巧(参见 references/termux-git-ssh.md),因为移动网络会屏蔽端口 22。

语音模式必须进行端到端测试才能宣布完成: chat.js 中的 Web Speech API 连接(setupVoice、startListening、speakText)与语音模式正常工作不是一回事。请通过实际对着手机麦克风说话并听到响应朗读出来进行测试。目标浏览器是 Android 上的 Chrome——重要的是它,而不是桌面 Chrome。

调试

端口 5000 已被占用:

fuser -k 5000/tcp  # 杀死端口 5000 上的任何进程
# 或使用 ss 进行检查:
ss -tlnp | grep 5000

Flask 应用无法启动/崩溃:

  • 检查语法:python3 -c "import py_compile; py_compile.compile('app.py', doraise=True)"
  • 启动前检查端口是否空闲
  • 使用 timeout 15 python3 app.py 运行以查看启动输出而不阻塞

API 返回 401/403/payment_required:

  • 密钥可能无效、已过期,或者模型不在您的套餐中
  • 在应用配置前,直接使用 curl 测试密钥
  • 检查 /v1/models 以查看您的套餐实际包含哪些模型

Pip 安装超时:

  • 移动设备上的 Termux 对于大型包的网络可能较慢/不可靠
  • 尽可能使用标准库替代方案(使用 urllib 代替 requests)
  • 如果必须安装,使用 pip3 install --timeout 30 <package> 以快速失败

强制终止的后台 Flask 进程会短暂保留端口:

  • 在 pkill -9 -f "[p]ython3 app.py" 之后,等待 2-3 秒再重启。
  • 如果遇到“Address already in use”,说明旧进程尚未完全释放套接字——等待更长时间。

在重启 Flask 服务器以加载代码更改之前,先杀死旧进程:

  • 模式:pkill -9 -f "[p]ython3 app.py" && sleep 3 && cd ~/sayip && python3 app.py &
  • 跳过杀死进程会导致“Address already in use”,新实例将无法启动。
  • 重启后务必使用 curl -s --max-time 3 http://127.0.0.1:5000/api/health 进行验证。

当用户报告 Web 应用损坏/JS 出错/无法工作时,按此顺序诊断:

  1. 确认服务器正在运行:curl -s --max-time 3 http://127.0.0.1:5000/api/health
  2. 检查静态文件是否返回 200:curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/static/style.css(以及 app.js, chat.js, voice.js, settings.js)
  3. 检查服务器实际提供的 HTML:curl -s http://127.0.0.1:5000/ | grep -E 'static/|style.css|app.js|chat.js|voice.js' ——确认 HTML 中的路径与 Flask 提供的路径匹配
  4. 如果静态文件返回 404 但 HTML 链接显示相对路径如 style.css 而非 /static/style.css,那就是错误——修补两个模板,使用 /static/ 前缀
  5. 阅读 JS 文件,检查所有 document.querySelector / getElementById / .querySelector 调用是否与 HTML 实际包含的内容匹配——缺少元素会导致整个脚本崩溃
  6. 检查空引用崩溃:在页面没有汉堡菜单元素的情况下调用 el("hamburger"),当 .wrap 不存在时调用 messagesEl.querySelector(".wrap"),当 .composer 不存在时调用 document.querySelector(".composer .wrap") ——这些都会崩溃 IIFE 并导致整个页面的 JS 失效
  7. 保护元素查找:var el = document.querySelector(selector); if (!el) return; 后再使用结果

在任何 HTML/模板编辑后,通过检查服务器实际提供的内容来验证前端修复:

  • 对页面 URL 执行 curl 并 grep 静态路径(/static/style.css、/static/js/app.js)确认修复已生效。
  • 直接对静态文件 URL 执行 curl,使用 -o /dev/null -w '%{http_code}' 确认返回 200。
  • 在两项检查都通过运行中的服务器之前,不要假设 HTML 编辑已生效。

此项目中最常见的“所有 JS 都坏了”的原因:

  • HTML 模板使用了相对静态路径(style.css、app.js、chat.js)而非 Flask 的 /static/ 前缀(/static/style.css、/static/js/app.js、/static/js/chat.js)。
  • 浏览器请求 http://127.0.0.1:5000/style.css → 404。没有 CSS,没有 JS 加载 → 页面无样式且无法工作。
  • 修复:修补两个 templates/ 文件(chat.html 和 settings.html),将所有相对静态路径替换为 /static/ 前缀路径。

Individual skills in this repo

This repo contains 12 individual skills — each has its own dedicated page.

相关技能