Skip to content

CSUlyc/MiniNotesAnnaApp

Repository files navigation

Mini Notes with LLM Summary

一个运行在 Anna App 平台上的迷你笔记应用。用户可以创建、查看、删除笔记,并通过本地 Executa Tool 调用 Host LLM 生成笔记总结。

本项目的核心目的是验证对 Anna App 平台模型的理解——不是普通 Web App,所有数据读写和 LLM 调用都必须通过 Anna Host API。


核心链路

Anna App iframe
  → AnnaAppRuntime.connect()                     ← 连接 Host
  → anna.storage.get / anna.storage.set          ← 读写笔记
  → anna.tools.invoke("tool-dev-mini-notes")     ← 调用 Executa Tool
  → Executa Tool (JSON-RPC 2.0 / stdio)          ← 本地工具进程
  → sampling/createMessage (reverse JSON-RPC)    ← 借用 Host LLM
  → summary 返回 UI

项目结构

mini-notes-app/
├── manifest.json                  # Anna App 清单(schema/permissions/ui/dev)
├── app.json                       # 应用元信息
├── package.json                   # pnpm workspace root
├── pnpm-workspace.yaml
│
├── ui/                            # 前端项目(React + TypeScript + Vite)
│   ├── index.html
│   ├── vite.config.ts
│   └── src/
│       ├── main.tsx               # 入口
│       ├── App.tsx                # 根组件(连接状态 + 组件组合)
│       ├── App.css                # 全局样式
│       ├── types.ts               # Note, InvokeEnvelope
│       ├── anna/
│       │   ├── runtime.ts         # AnnaAppRuntime.connect() 封装
│       │   ├── storage.ts         # anna.storage.get/set 封装
│       │   └── tools.ts           # anna.tools.invoke 封装
│       ├── hooks/
│       │   └── useNotes.ts        # 笔记 CRUD 状态管理
│       └── components/
│           ├── NoteInput.tsx       # 输入框 + 保存
│           ├── NoteList.tsx        # 笔记列表
│           ├── NoteItem.tsx        # 单条笔记(含删除)
│           ├── EmptyState.tsx      # 空状态
│           ├── ErrorBanner.tsx     # 错误提示
│           └── Summarize.tsx       # 总结按钮 + 结果
│
├── executas/
│   └── tool-dev-mini-notes/       # Executa Tool(Node.js + TypeScript)
│       ├── executa.json           # Executa manifest
│       ├── package.json           # sentinel(node type)
│       ├── tsconfig.json
│       ├── scripts/
│       │   └── bundle.mjs         # esbuild + SEA 打包脚本
│       └── src/
│           ├── index.ts           # 入口(stdio 事件循环)
│           ├── rpc.ts             # JSON-RPC 2.0 框架
│           ├── types.ts           # 类型定义
│           └── handlers/
│               ├── initialize.ts  # v2 协商 + sampling 能力声明
│               ├── describe.ts    # 工具 manifest
│               ├── invoke.ts      # summarize 处理 + sampling 调用
│               ├── health.ts      # 健康检查
│               └── shutdown.ts    # 优雅退出
│
├── fixtures/
│   └── sampling-mock.jsonl        # Mock sampling fixture
│
├── scripts/
│   ├── build-executa.sh           # 本机二进制打包(macOS/Linux)
│   ├── build-executa.ps1          # 本机二进制打包(Windows)
│   ├── test-rpc.sh                # JSON-RPC 手动测试(21 项)
│   └── test-integration.md        # 集成测试指南
│
└── .github/
    └── workflows/
        └── release.yml            # 三平台构建 + GitHub Release

快速开始

前置要求

  • Node.js >= 18
  • pnpm >= 9(npm install -g pnpm
  • uv(Python 包运行器,anna-app dev 需要)
    # macOS/Linux
    curl -LsSf https://astral.sh/uv/install.sh | sh
    # Windows PowerShell
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
  • @anna-ai/cli(Anna App 开发 CLI)
    npm install -g @anna-ai/cli

安装依赖

pnpm install

构建

构建前端 bundle

# 仅构建前端
pnpm --filter ui build

# 构建全部(前端 + Executa)
pnpm build

前端构建产物输出到 bundle/ 目录,这是 Anna App 加载的静态文件。

校验 manifest

pnpm build && anna-app validate --strict

预期输出:✓ validate passed


本地开发与测试

1. 启动 UI Harness(--no-llm 模式)

pnpm dev
# 等价于:pnpm build && anna-app dev --no-llm

打开 http://localhost:5180/,你应该看到:

  • Mini Notes App 加载
  • "✅ 已连接到 Anna App Runtime"
  • 输入框和空状态

在 UI 中测试:

  1. 输入笔记文字 → 点击保存(或按 Enter)→ 笔记出现在列表中
  2. 点击笔记右侧的 ✕ 删除笔记
  3. 点击 "✨ Summarize 全部笔记" → 预期显示错误

为什么 --no-llm 下 Summarize 会报错?

--no-llm 禁用了 Host 的 LLM/sampling 能力。当 Executa Tool 发起 sampling/createMessage 反向请求时,Harness 拒绝该请求并返回错误 [-32603] harness started with --no-llm

这是预期行为——说明 anna.tools.invoke → Executa → sampling 的 完整链路是通的,只是 LLM 被禁用了。后端 sampling 的正确性通过下面的 mock fixture 单独验证。

2. 后端 Executa Sampling 测试(Mock)

pnpm executa:dev
# 等价于:anna-app executa dev \
#   --dir executas/tool-dev-mini-notes \
#   --mock-sampling fixtures/sampling-mock.jsonl \
#   --invoke summarize \
#   --args '{"notes":[{"content":"明天跟客户 follow up"},{"content":"修复登录 bug"},{"content":"Workshop 内容想法"}]}'

预期输出:

{
  "success": true,
  "data": {
    "summary": "根据你记录的笔记,主要涉及三个方面:1)客户跟进与商务沟通..."
  }
}

确认 sampling/createMessage 被发起:查看 stderr 日志,应包含:

Sending sampling/createMessage id=sampling-...
Sampling complete: model=mock-model, length=...

这证明 Executa Tool 确实发起了反向 sampling/createMessage 请求,并且从 fixture 中获得了模拟的 LLM 响应。

3. 手动 JSON-RPC 测试

bash scripts/test-rpc.sh

覆盖 initializedescribeinvokehealth、错误处理(未知 method、无效 JSON),共 21 项断言。


确认调用链路

确认 notes 存储走 anna.storage.*

  1. 打开 ui/src/anna/storage.ts
  2. 所有笔记读写都通过 anna.storage.get({ key: "notes" })anna.storage.set({ key: "notes", value: [...] })
  3. 前端代码中不存在 localStorageIndexedDB 或直接 fetch 调用
  4. 浏览器 DevTools Network 面板可观测 postMessage 通信

调用链追踪:

NoteInput.onSave
  → useNotes.addNote(content)
    → saveNotes(newNotes)                     ← storage.ts:31
      → anna.storage.set({ key, value })      ← Host API

useNotes.refresh (on mount)
  → loadNotes()                               ← storage.ts:17
    → anna.storage.get({ key })               ← Host API

NoteItem.onDelete
  → useNotes.removeNote(index)
    → saveNotes(filtered)                     ← storage.ts:31
      → anna.storage.set({ key, value })      ← Host API

确认 summary 走 anna.tools.invoke → Executa → sampling/createMessage

  1. 打开 ui/src/anna/tools.ts
  2. Summarize 通过 anna.tools.invoke({ tool_id: "tool-dev-mini-notes", method: "summarize", args: { notes } }) 调用
  3. Executa Tool 的 invoke.ts 处理请求并发起 sampling/createMessage 反向 RPC
  4. fixtures/sampling-mock.jsonl 提供 mock LLM 响应

调用链追踪:

Summarize.handleSummarize
  → summarizeNotes(notes)                     ← tools.ts:19-22
    → anna.tools.invoke({ tool_id, args })    ← Host API
      → Executa invoke handler                ← invoke.ts
        → sendRequest(sampling/createMessage) ← 反向 JSON-RPC
          → Host mock fixture 响应            ← sampling-mock.jsonl
        → { success: true, data: { summary } }

二进制打包

打包方案

使用 esbuild 将 TypeScript 源码打包为单个 CommonJS 文件,再使用 Node.js SEA(Single Executable Applications)将打包结果注入为独立可执行二进制。

TypeScript 源码
  → esbuild bundle(单文件 CJS)
    → node --experimental-sea-config(SEA blob)
      → postject(注入 node 二进制)
        → archive(tar.gz / zip)

本机打包

# macOS / Linux
bash scripts/build-executa.sh --archive

# Windows PowerShell
.\scripts\build-executa.ps1 -Archive

产物输出到 executas/tool-dev-mini-notes/sea/

  • tool-dev-mini-notes-{platform}.tar.gz(macOS/Linux)
  • tool-dev-mini-notes-{platform}.zip(Windows)

Archive 结构

tool-dev-mini-notes-{platform}.{tar.gz|zip}
└── tool-dev-mini-notes-{platform}/
    ├── manifest.json          ← 二进制分发 manifest(tool_id, platform, entrypoint)
    └── bin/
        └── tool-dev-mini-notes{.exe}  ← 独立可执行二进制

支持的 platform key:

  • darwin-arm64
  • darwin-x86_64
  • windows-x86_64

GitHub Actions 发布

触发方式

触发 行为
workflow_dispatch(手动) 构建三平台二进制,上传为 workflow artifacts
push tag v*(推送标签) 构建三平台二进制,创建 GitHub Release 并上传 assets

Release Assets

一次发布生成三个平台的产物:

Platform Asset
macOS Apple Silicon tool-dev-mini-notes-darwin-arm64.tar.gz
macOS Intel tool-dev-mini-notes-darwin-x86_64.tar.gz
Windows x64 tool-dev-mini-notes-windows-x86_64.zip

每个平台的 job 包含构建后的 smoke test(describe 验证响应包含正确的 tool_id)。


Anna App 平台概念解释

概念 说明 在本项目中
manifest.json Anna App 清单文件,声明 App 的权限、UI、Executa 依赖和本地开发配置 根目录 manifest.json,声明了 tools.invokestorage.* 权限
bundle 前端静态文件(HTML + JS + CSS),由 Vite 构建 bundle/ 目录,manifest.jsonui.bundle.entry 指向 index.html
Executa Tool 本地工具进程,通过 JSON-RPC 2.0 over stdio 与 Anna Host 通信 executas/tool-dev-mini-notes/,type 为 node
Anna Storage / APS KV Anna 平台的键值存储 API,App 通过 anna.storage.get/set 读写数据 笔记存储在 key "notes" 下,本地无 login 时使用 legacy in-memory runtime_state
Sampling Executa Tool 通过反向 JSON-RPC sampling/createMessage 向 Host 请求 LLM 生成 invoke.ts 中发起 sampling 请求,本地测试用 --mock-sampling fixture
Binary Archive 将 Executa Tool 打包为独立可执行二进制,用于分发 scripts/build-executa.* + GitHub Actions 生成三平台二进制

关系图

manifest.json
├── 声明 required_executas → tool-dev-mini-notes
├── 声明 ui.bundle         → bundle/index.html (Vite 构建产物)
├── 声明 ui.host_api       → storage: [get, set]
│                           → tools:   [tool-dev-mini-notes]
└── 声明 dev               → 本地开发配置

Anna App Runtime (Host)
├── 加载 bundle/           → 前端 iframe
├── 注入 SDK               → /static/anna-apps/_sdk/latest/index.js
├── 提供 storage API       → anna.storage.get/set → legacy memory (dev)
├── 路由 tools.invoke      → 启动 Executa Tool (JSON-RPC/stdio)
└── 响应 sampling          → mock fixture (dev) 或真实 LLM (prod)

更多文档


不要求

  • ❌ 真实 Anna 账号 / anna-app login
  • ❌ 真实 LLM API key
  • ❌ 云端 APS 存储
  • ❌ 笔记持久化跨 dev 重启(legacy in-memory runtime_state)
  • ❌ 线上发布 Anna App
  • ❌ 代码签名 / 公证 / Windows Authenticode
  • ❌ 编辑、搜索、标签、多笔记本、富文本、附件、协同

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors