[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105286":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":12,"contributorsCount":12,"subscribersCount":12,"size":12,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":12,"forks30d":12,"starsTrendScore":15,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":32,"readmeContent":33,"aiSummary":9,"trendingCount":12,"starSnapshotCount":12,"syncStatus":34,"lastSyncTime":35,"discoverSource":36},105286,"OpenMinutes","floatboatai\u002FOpenMinutes","floatboatai","OpenMinutes · 录音笔工作台：MemoTalk S1 录音的导入 · 转录 · AI 归类 · 检索复盘（Floatboat Workbench 开源实现）",null,"TypeScript",108,0,70,12,36,63.2,"MIT License",false,"main",true,[22,23,24,25,26,27,28,29,30,31],"ai-classification","floatboat","memotalk","react","speech-to-text","sqlite","transcription","typescript","voice-recorder","workbench","2026-11-01 00:13:30","English | [简体中文](README.zh-CN.md)\n\n# OpenMinutes\n\n> Turn MemoTalk S1 voice-recorder MP3s into searchable, categorized text you can review.\n\n`OpenMinutes` (formerly `memotalk-workbench`) is a complete open-source [Floatboat](https:\u002F\u002Ffloatboat.ai) Workbench app, used with the [`memotalk-sdk`](https:\u002F\u002Fgithub.com\u002Ffloatboatai\u002Fmemotalk-sdk) hardware client.\n\n---\n\n## Preview\n\n### MemoTalk S1\n\n![MemoTalk S1 recorder shown from multiple angles](docs\u002Fimages\u002Fmemotalk-s1.png)\n\n### OpenMinutes Workbench\n\n![OpenMinutes recording overview with device status, recording statistics, categories, and recent recordings](docs\u002Fimages\u002Fopenminutes-workbench.png)\n\n## Why OpenMinutes?\n\nRecordings accumulate every day, but finding a particular conversation or listening back to everything becomes difficult.\n\nThis workbench connects import, transcription, classification, and search in one workflow. Each recording has a clear stage label: awaiting download, awaiting transcription, transcription queued, transcribing, awaiting classification, classification queued, classifying, archived, or failed. Batch operations use the selected recordings.\n\n## Features\n\n| Capability | Description |\n| --- | --- |\n| Three import methods | Scan a directory, register a device file manifest, or download directly over BLE (requires local Python, `bleak`, and a physical device) |\n| Streaming transcription | Decode audio to 16 kHz mono PCM16 with `ffmpeg`, stream it through Host ASR, and store the full text and timestamped segments |\n| AI classification | Use the Host LLM to generate structured titles, summaries, categories, tags, key points, action items, and importance levels, with manual editing |\n| Search and review | Search transcript text, titles, and summaries; filter by category, tag, or stage; sort by time; and process recordings in batches |\n| Recording details and playback | Play audio with HTTP Range support, seek to sentence timestamps, and review separate transcript, AI analysis, and execution-history sections |\n| Category management | Create, rename inline, recolor, and delete custom categories; built-in categories cannot be deleted |\n| Device setup | Check the environment (Python, `bleak`, SDK, and `ffmpeg`), configure the import directory, browse the SDK command reference, and register recordings manually |\n| Live captions | Stream device audio through Host ASR, then save the recording and any transcript when stopped |\n\n## Getting started\n\nThe app runs in the **Floatboat desktop Workbench runtime**. It is a workbench package loaded by the Host, not a standalone Node service.\n\n- The Host prepares dependencies, runs strict TypeScript checks, compiles the server, and bundles the UI. Generated files go in `dist\u002F` and are not committed.\n- The Host applies SQLite migrations when opening the workbench. Add new files to `migrations\u002F`; do not rewrite existing migrations.\n- ASR and LLM calls use Host capabilities. This repository neither contains nor requires model API keys.\n\n```bash\npnpm install          # Install UI dependencies, type definitions, and test tooling\n```\n\nThen use Floatboat's `check_workbench_project` to run type checks and build the app, and `workbench_runtime open` to apply the update. These are Host operations, not package scripts.\n\n### Runtime dependencies\n\n| Dependency | Purpose | Behavior when missing |\n| --- | --- | --- |\n| `ffmpeg` | Audio probing and decoding | Transcription is unavailable, and the UI reports it |\n| `python` + `bleak` + `memotalk-sdk` | Download device recordings over BLE | The device page reports \"not ready\"; directory import remains available |\n\nTool discovery tries configured paths, Floatboat's bundled tools, `PATH`, and command names in that order, so it does not rely solely on the process `PATH`.\n\nCheck the device page for the Python interpreter in use, then install the SDK into that same environment:\n\n```bash\nuv pip install --python \"\u002Fpath\u002Fto\u002Fpython\" \"memotalk-sdk>=0.1.0\"\n\"\u002Fpath\u002Fto\u002Fpython\" -m memotalk.cli --version\n```\n\nOn Windows, use the absolute path to `python.exe`. In PowerShell, prefix the quoted interpreter path with `&` when running the version check. If you use a virtual environment, select its Python interpreter in settings.\nThe SDK installs `bleak` automatically; you do not need to clone its source or configure an SDK directory.\nThe app uses `from memotalk import MemoTalkClient` for device APIs and live audio streaming.\nUpgrading removes the old `sdk_dir` setting while preserving other path, recognition, and automation settings.\n\n### SDK regression tests\n\n`pnpm test` runs the server tests. SDK integration tests use the package's simulated device and do not connect to a physical recorder.\nSet the `MEMOTALK_TEST_PYTHON` environment variable to the path of a Python interpreter with `memotalk-sdk` installed before running them.\nWithout this variable, SDK integration tests are skipped; settings API and migration tests still run.\n\n## Project structure\n\n```text\nsrc\u002F\n  contracts\u002F       Shared DTOs (app.ts), input validation (input.ts), busy states (recording-status.ts)\n  server\u002F          Node server: assembly, routing, and domain logic\n    index.ts       Server assembly and automatic-connection startup\n    routes\u002F        HTTP routes grouped by domain\n    app-route.ts   App metadata, overview, and tag-list routes\n    http.ts        Shared API envelope and top-level error\u002Fcode mapping\n    sql.ts         Thin node:sqlite wrapper, settings, and job records\n    repo.ts        Queries, transient queued-status projection, and library-wide hasActiveTasks\n    library.ts     Library root, monthly folders, and legacy audio-path resolution\n    ingest.ts      Directory scanning, manifest registration, deduplication, naming conflicts\n    pull.ts        BLE download staging, library attachment, and pending-file pulls\n    auto-connect.ts Background device checks and optional automatic pulls\n    media.ts       ffmpeg probing, duration parsing, and PCM16 framing\n    transcribe.ts  Host ASR sessions: parameter fallback, streaming, resumption, error classification\n    organize.ts    Host LLM structured classification and text fallback\n    pipeline.ts    Transcription\u002Fclassification state machines and retries\n    jobs.ts        Task scheduling and automatic transcription after import\u002Fdownload\n    queue.ts       Serial task execution, pending\u002Frunning labels, and task-state queries\n    bridge.ts      memotalk-sdk Python API bridge (fixed arguments, no concatenated commands)\n    sdk-script.ts  Python script for SDK status, listing, and download actions\n    live.ts        Live audio capture, file storage, and session persistence\n    live-audio.ts  Live MP3 decoder arguments and one-second PCM framing\n    live-asr.ts    On-demand live ASR sessions and frame streaming\n    live-script.ts Python script for SDK live audio streaming\n  ui\u002F              React UI: overview \u002F library \u002F details \u002F device \u002F categories \u002F live \u002F settings\nmigrations\u002F        Append-only SQLite migrations\nsdk\u002F               Host-provided Workbench runtime, clients, contracts, and types\ntests\u002F             Server tests\nmanifest.json      Host entry points, build profile, capabilities, and audio resource\nindex.html         UI build entry point\n```\n\nRelated documentation (in Chinese): [requirements](REQUIREMENTS.md), [engineering conventions](ENGINEERING.md), and [design](DESIGN.md).\n\n## Data model\n\n`devices`, `recordings`, `transcripts`, `analyses`, `categories`, `tags`, `recording_tags`, `job_runs`, `sdk_commands`, and `settings`.\n\n### Storage and directories\n\n- `import_dir` is the scan source and the default audio export destination, defaulting to `~\u002Fmemo\u002Fmemotalk\u002Finbox`. Scans create a missing directory, read only its immediate files, and copy supported audio (`.mp3`, `.wav`, `.m4a`, `.aac`, `.ogg`, `.flac`, `.amr`, `.opus`) into the library without removing the originals.\n- `library_dir` defaults to `~\u002Fmemo\u002Fmemotalk\u002Fraw`. Imports and BLE downloads are copied into `\u003Clibrary_dir>\u002F\u003Cyyyy-MM>\u002F`, using the recording month inferred from the filename or a fallback date. Live recordings also go into monthly folders. Use an absolute path or `~\u002F...` for these directory settings; changing the library setting does not relocate existing files.\n- New `recordings.storage_path` values store the full destination path (absolute with the default or an absolute library directory). Historical relative filenames resolve against the Host's `audio` data resource. BLE downloads first use the Host's `audio\u002F_incoming` staging directory, not `import_dir`.\n- Deletion attempts to move audio to `\u003Clibrary_dir>\u002F_trash\u002F\u003Cmilliseconds>-\u003Cbasename>` using the **current** library setting, then deletes the recording and its transcript, analysis, tag links, and job history. If the move fails, database deletion still proceeds; `audioMovedToTrash` is then `null`. There is no restore API.\n- Export copies the audio under its original `filename` into `import_dir`, or into the request body's optional `directory`. It creates missing destination directories and can overwrite a same-named file. The detail page uses the default destination. There is no separate export-directory setting.\n\nWhen resolving `import_dir`, an empty value falls back to the default; a path inside the system temporary directory is replaced with the default and written back to settings. An explicit `directory` in a scan request is used and saved directly, without that temporary-directory fallback.\n\n## Settings\n\nThe settings page edits paths, automation, and recognition options. The device page edits only `import_dir`. `PATCH \u002Fapi\u002Fsettings` accepts `{ \"values\": { \"key\": \"value\" } }`; all values must be strings.\n\n| Keys | Behavior and defaults |\n| --- | --- |\n| `library_dir`, `import_dir` | Empty by default; resolved as described above; at most 500 characters each |\n| `python_path`, `ffmpeg_path` | Empty by default for automatic discovery; at most 500 characters each |\n| `auto_transcribe`, `auto_analyze` | Automatically transcribe after import\u002Fdownload and classify after transcription; both default to `\"1\"` |\n| `auto_connect`, `auto_pull` | Background device checks and pending-file downloads after a successful automatic connection; both default to `\"1\"` |\n| `auto_connect_minutes` | Integer string from `\"1\"` to `\"120\"`, default `\"3\"` |\n| `speaker_diarization` | File-transcription speaker separation, default `\"1\"` |\n| `asr_hotwords` | File-transcription hotwords, empty by default; at most 300 characters |\n| `asr_language` | At most 20 characters; the UI offers automatic (empty), `zh`, `en`, and `ja`. Currently saved as file-transcription metadata, but not passed to ASR |\n| `device_serial` | At most 64 characters; saved by device operations or via the API, not a field on the settings page |\n\nFlags accept only `\"0\"` or `\"1\"`. Background checks run while the workbench server is open; manual status checks do not trigger automatic pulls. Live ASR currently uses fixed PCM parameters rather than the file-transcription hotword, language, or speaker settings. `GET \u002Fapi\u002Fsettings` can also return runtime-written `last_auto_connect_at` and `last_auto_pull_at`; these are not writable through PATCH. The removed `sdk_dir` is hidden on reads and ignored on writes; other unknown keys are rejected.\n\n## API overview\n\n| Method | Path | Description |\n| --- | --- | --- |\n| `GET` | `\u002Fapi\u002Fapp` | Application name and version |\n| `GET` | `\u002Fapi\u002Foverview` | Recording statistics, device status, and overview data |\n| `GET` | `\u002Fapi\u002Ftags` | Tag names for filtering |\n| `GET` | `\u002Fapi\u002Frecordings` | List recordings with search, stage\u002Fcategory\u002Ftag\u002Ffavorite\u002Farchive filters, sorting, and pagination; data contains `items`, `total`, and library-wide `hasActiveTasks` |\n| `GET` | `\u002Fapi\u002Frecordings\u002F:id` | Details: metadata, transcript, AI analysis, and execution history |\n| `GET` | `\u002Fapi\u002Frecordings\u002F:id\u002Ftags` | Tag IDs and names for one recording |\n| `PATCH` | `\u002Fapi\u002Frecordings\u002F:id` | Edit title, summary, category, tags, importance, favorite, and archive flags |\n| `DELETE` | `\u002Fapi\u002Frecordings\u002F:id` | Delete a recording; attempt to move audio to `\u003Clibrary_dir>\u002F_trash\u002F\u003Cmilliseconds>-\u003Cbasename>` |\n| `GET` | `\u002Fapi\u002Frecordings\u002F:id\u002Faudio` | Audio stream with HTTP Range support (206 and `content-range`; 416 for out-of-range requests) |\n| `POST` | `\u002Fapi\u002Frecordings\u002F:id\u002Fexport` | Copy audio to `import_dir` by default; optional body field `directory` overrides it |\n| `POST` | `\u002Fapi\u002Frecordings\u002F:id\u002Ftranscribe` | Queue transcription; after prerequisite checks, reject already queued\u002Frunning recordings with `BUSY` |\n| `POST` | `\u002Fapi\u002Frecordings\u002F:id\u002Forganize` | Queue AI classification; after prerequisite checks, reject already queued\u002Frunning recordings with `BUSY` |\n| `POST` | `\u002Fapi\u002Frecordings\u002Fbatch\u002Ftranscribe`, `\u002Fapi\u002Frecordings\u002Fbatch\u002Forganize` | Queue transcription or classification for body field `ids`; skip already queued\u002Frunning or otherwise ineligible recordings |\n| `GET` | `\u002Fapi\u002Fingest\u002Fdirectory` | Read import directory information |\n| `POST` | `\u002Fapi\u002Fingest\u002Fdirectory\u002Fensure` | Create the import directory |\n| `POST` | `\u002Fapi\u002Fingest\u002Fscan` | Scan the import directory |\n| `POST` | `\u002Fapi\u002Fingest\u002Fmanifest` | Register device manifest entries |\n| `GET` | `\u002Fapi\u002Fdevice` | Environment checks, SDK command reference, Host capability summary, device records, and settings |\n| `GET` | `\u002Fapi\u002Fdevice\u002Fsummary` | Device\u002Flocal recording counts and device\u002Fcategory counts |\n| `GET` | `\u002Fapi\u002Fdevice\u002Fcapabilities` | Host ASR\u002FLLM operation availability, not BLE commands |\n| `DELETE` | `\u002Fapi\u002Fdevice\u002F:id` | Remove a local device record and clear its matching saved serial; does not delete recordings or device files |\n| `POST` | `\u002Fapi\u002Fdevice\u002Fcheck`, `\u002Fapi\u002Fdevice\u002Flist`, `\u002Fapi\u002Fdevice\u002Fdownload`, `\u002Fapi\u002Fdevice\u002Fstatus` | Environment check, file listing, BLE download, and status |\n| `GET` | `\u002Fapi\u002Flive` | Live caption status |\n| `POST` | `\u002Fapi\u002Flive\u002Fstart`, `\u002Fapi\u002Flive\u002Fstop` | Start or stop live captions |\n| `GET`, `POST` | `\u002Fapi\u002Fcategories` | List or create categories |\n| `PATCH`, `DELETE` | `\u002Fapi\u002Fcategories\u002F:id` | Update or delete a category |\n| `GET`, `PATCH` | `\u002Fapi\u002Fsettings` | Read settings or update the writable keys listed above |\n\nJSON responses use the `{ ok: true, data }` \u002F `{ ok: false, error: string, code: string }` envelope. The audio endpoint returns an audio stream on success.\n\n### Queued tasks and polling\n\n- `queued_transcribe` (\"transcription queued\") and `queued_organize` (\"classification queued\") are temporary statuses projected by the server from its in-memory queue into recording summaries and details, not stored in SQLite. Stored `transcribing` \u002F `analyzing` statuses take precedence. Stage filters still use stored statuses. Code: `src\u002Fserver\u002Fqueue.ts:25`, `src\u002Fserver\u002Frepo.ts:30`, `src\u002Fserver\u002Frepo.ts:59`, `src\u002Fserver\u002Frepo.ts:202`.\n- `GET \u002Fapi\u002Frecordings` returns `data.hasActiveTasks`: whether any transcription\u002Fclassification task is queued or running across the library. It checks queue labels and stored running statuses, independently of search, filters, and pagination. Code: `src\u002Fserver\u002Frepo.ts:150`, `src\u002Fcontracts\u002Fapp.ts:115`.\n- Single-recording transcription\u002Fclassification checks audio\u002Ftext prerequisites first, then returns `BUSY` for an already queued or running recording. Batch operations skip such recordings and report `accepted`, `skipped`, and accepted `ids`. Code: `src\u002Fserver\u002Froutes\u002Fpipeline.ts:25`, `src\u002Fserver\u002Froutes\u002Fpipeline.ts:46`, `src\u002Fserver\u002Froutes\u002Fpipeline.ts:66`, `src\u002Fserver\u002Froutes\u002Fpipeline.ts:84`.\n- The detail page polls every 2.6 seconds while its recording is queued or running and stops when that recording leaves those states. The library polls at the same interval while `hasActiveTasks` is true, even if active recordings are outside the current results; it stops when no queued or running tasks remain across the library. Code: `src\u002Fcontracts\u002Frecording-status.ts:1`, `src\u002Fui\u002Fpages\u002FDetail.tsx:51`, `src\u002Fui\u002Fpages\u002FLibrary.tsx:54`, `src\u002Fui\u002FuseApi.ts:48`.\n- `queue.ts` retains waiting and running task labels until `finally` removes them, including after failure, and exposes task-state queries. `repo.ts` combines these queries with stored statuses to build response statuses and `hasActiveTasks`. Code: `src\u002Fserver\u002Fqueue.ts:8`, `src\u002Fserver\u002Fqueue.ts:25`, `src\u002Fserver\u002Fqueue.ts:31`, `src\u002Fserver\u002Frepo.ts:59`, `src\u002Fserver\u002Frepo.ts:150`.\n\n## Design principles\n\n- Report failures honestly. If BLE is not ready, audio cannot play, or upstream ASR fails, the UI reports the problem and offers a next action.\n- Communicate status with both text and color, never color alone. Animations respect `prefers-reduced-motion`.\n- Prioritize lists. Each recording occupies one row, content is at most 1220 px wide, and table numbers use `tabular-nums` for alignment.\n- Support accessibility. Icon-only buttons have `aria-label` and `title`; segmented controls use `aria-pressed`.\n\nSee [DESIGN.md](DESIGN.md) for more design decisions.\n\n## Related repositories\n\n- [`floatboatai\u002Fmemotalk-sdk`](https:\u002F\u002Fgithub.com\u002Ffloatboatai\u002Fmemotalk-sdk): Python BLE client and CLI for the MemoTalk S1\n\n## License\n\n[MIT](LICENSE) © Floatboat and MemoTalk\n",2,"2026-10-31 04:26:20","CREATED_QUERY"]