From acfba090e4fd85cebdceb493c76bd30174cf590d Mon Sep 17 00:00:00 2001 From: Travis Vasceannie Date: Sat, 24 Jan 2026 14:50:19 +0000 Subject: [PATCH] feat: Introduce Gitea CI/CD workflows, refactor Docker deployment with dedicated dev/prod compose files and enhanced ROCm GPU support, and update RAG documentation for new AI and ASR infrastructure. --- .gitea/workflows/build.yml | 118 ++++++++ .gitea/workflows/ci.yml | 103 +++++++ .gitea/workflows/proto-sync.yml | 103 +++++++ .gitea/workflows/quality.yml | 141 ++++++++++ .rag/01-architecture-overview.md | 175 +++++++++++- .rag/02-domain-entities.md | 49 +++- .rag/03-domain-ports.md | 95 ++++--- .rag/04-application-services.md | 193 ++++++++++--- .rag/05-infrastructure-adapters.md | 201 +++++++++++--- .rag/06-grpc-layer.md | 208 +++++++++----- .rag/07-typescript-api-layer.md | 354 +++++++++++++++++------- .rag/08-typescript-hooks-contexts.md | 206 +++++++++----- .rag/09-typescript-components-pages.md | 156 +++++++---- .rag/10-rust-tauri-commands.md | 106 ++++++- .rag/11-rust-grpc-types.md | 5 + .rag/12-rust-state-audio-crypto.md | 9 + .rag/13-common-utilities.md | 20 +- .rag/14-testing.md | 233 ++++++++++++++++ README.md | 55 ++-- compose.gpu.yaml | 36 --- compose.yaml => compose.yaml.deprecated | 0 docker-bake.hcl | 294 ++++++++------------ docker-compose.dev.yml | 207 ++++++++++++++ docker-compose.prod.yml | 169 +++++++++++ docker/Dockerfile.rocm | 94 +------ docker/entrypoint-gpu.sh | 15 +- docker/server-gpu.Dockerfile | 127 ++++----- docker/server.Dockerfile | 117 ++++---- pyproject.toml | 5 +- 29 files changed, 2676 insertions(+), 918 deletions(-) create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/proto-sync.yml create mode 100644 .gitea/workflows/quality.yml create mode 100644 .rag/14-testing.md delete mode 100644 compose.gpu.yaml rename compose.yaml => compose.yaml.deprecated (100%) create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.prod.yml diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..88e30d7 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,118 @@ +name: Build + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + release: + description: "Create GitHub release" + required: false + default: false + type: boolean + +env: + NODE_VERSION: "22" + RUST_TOOLCHAIN: "stable" + +jobs: + build-tauri: + strategy: + fail-fast: false + matrix: + include: + - platform: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + artifact_suffix: linux-x64 + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies (Linux) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + libappindicator3-dev \ + librsvg2-dev \ + patchelf \ + libasound2-dev \ + libsndfile1 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: client/package-lock.json + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + targets: ${{ matrix.target }} + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + client/src-tauri/target/ + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('client/src-tauri/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo- + ${{ runner.os }}-cargo- + + - name: Install frontend dependencies + run: cd client && npm ci + + - name: Build Tauri application + run: cd client && npm run tauri build -- --target ${{ matrix.target }} + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + + - name: Upload Linux artifacts + if: matrix.platform == 'ubuntu-22.04' + uses: actions/upload-artifact@v4 + with: + name: noteflow-${{ matrix.artifact_suffix }} + path: | + client/src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb + client/src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage + client/src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm + if-no-files-found: warn + + release: + needs: build-tauri + if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.release == 'true' + runs-on: ubuntu-22.04 + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + draft: true + generate_release_notes: true + files: | + artifacts/**/*.deb + artifacts/**/*.AppImage + artifacts/**/*.rpm + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..01e3c16 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,103 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +env: + PYTHON_VERSION: "3.12" + NODE_VERSION: "22" + RUST_TOOLCHAIN: "stable" + +jobs: + test-python: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libsndfile1 portaudio19-dev + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run unit tests (non-integration) + run: pytest tests/ -m "not integration and not slow" --asyncio-mode=auto -v + + - name: Run integration tests (testcontainers) + run: pytest tests/integration/ -m "integration" --asyncio-mode=auto -v + + test-typescript: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: client + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: client/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run Vitest tests + run: npm run test + + test-rust: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: client/src-tauri + + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies (Tauri) + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + libappindicator3-dev \ + librsvg2-dev \ + patchelf \ + libasound2-dev + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + client/src-tauri/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('client/src-tauri/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Run Rust tests + run: cargo test --lib diff --git a/.gitea/workflows/proto-sync.yml b/.gitea/workflows/proto-sync.yml new file mode 100644 index 0000000..73f399d --- /dev/null +++ b/.gitea/workflows/proto-sync.yml @@ -0,0 +1,103 @@ +name: Proto Sync + +on: + push: + paths: + - "src/noteflow/grpc/proto/**" + branches: + - main + - develop + +env: + PYTHON_VERSION: "3.12" + RUST_TOOLCHAIN: "stable" + +jobs: + regenerate-stubs: + runs-on: ubuntu-22.04 + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GIT_TOKEN }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Python gRPC tools + run: | + python -m pip install --upgrade pip + pip install grpcio-tools + + - name: Regenerate Python gRPC stubs + run: | + python -m grpc_tools.protoc \ + -I src/noteflow/grpc/proto \ + --python_out=src/noteflow/grpc/proto \ + --grpc_python_out=src/noteflow/grpc/proto \ + --pyi_out=src/noteflow/grpc/proto \ + src/noteflow/grpc/proto/noteflow.proto + + - name: Fix Python imports (relative imports) + run: | + sed -i 's/^import noteflow_pb2/from . import noteflow_pb2/' \ + src/noteflow/grpc/proto/noteflow_pb2_grpc.py + + - name: Install system dependencies (Tauri/Rust) + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + libappindicator3-dev \ + librsvg2-dev \ + patchelf \ + libasound2-dev \ + protobuf-compiler + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + client/src-tauri/target/ + key: ${{ runner.os }}-cargo-proto-${{ hashFiles('client/src-tauri/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Regenerate Rust types (via cargo build) + run: | + cd client/src-tauri + cargo build --lib 2>&1 || echo "Rust build completed (proto regeneration triggered)" + + - name: Check for changes + id: check_changes + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Commit and push regenerated stubs + if: steps.check_changes.outputs.changes == 'true' + run: | + git config --global user.name "vasceannie" + git config --global user.email "vasceannie@users.noreply.gitea.local" + git add -A + git commit -m "chore: auto-regenerate gRPC stubs [skip ci]" + git push + env: + GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitea/workflows/quality.yml b/.gitea/workflows/quality.yml new file mode 100644 index 0000000..4df2591 --- /dev/null +++ b/.gitea/workflows/quality.yml @@ -0,0 +1,141 @@ +name: Quality + +on: + pull_request: + branches: [main, develop] + +env: + PYTHON_VERSION: "3.12" + NODE_VERSION: "22" + RUST_TOOLCHAIN: "stable" + +jobs: + quality-python: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Create hygiene directory + run: mkdir -p .hygeine + + - name: Run Basedpyright type checking + run: basedpyright + + - name: Run Ruff linting + run: ruff check . + + - name: Run Python quality tests + run: pytest tests/quality/ -q + + quality-typescript: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: client + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: client/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Create hygiene directory + run: mkdir -p ../.hygeine + + - name: Run TypeScript type checking + run: npm run type-check + + - name: Run Biome linting + run: npx biome lint . --reporter=github + + - name: Run TypeScript quality tests + run: npm run test:quality + + quality-rust: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: client/src-tauri + + steps: + - uses: actions/checkout@v4 + + - name: Install system dependencies (Tauri) + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.1-dev \ + libappindicator3-dev \ + librsvg2-dev \ + patchelf \ + libasound2-dev + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: rustfmt, clippy + + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + client/src-tauri/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('client/src-tauri/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --check + + - name: Run Clippy + run: cargo clippy -- -D warnings + + format-check: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: client/package-lock.json + + - name: Install client dependencies + run: cd client && npm ci + + - name: Check Biome formatting + run: cd client && npm run format:check + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + components: rustfmt + + - name: Check Rust formatting + run: cd client/src-tauri && cargo fmt --check diff --git a/.rag/01-architecture-overview.md b/.rag/01-architecture-overview.md index d1908d9..7942535 100644 --- a/.rag/01-architecture-overview.md +++ b/.rag/01-architecture-overview.md @@ -4,8 +4,8 @@ Intelligent meeting notetaker: local-first audio capture + navigable recall + evidence-linked summaries. ## Tech Stack -- **Python Backend**: gRPC server, domain logic, infrastructure adapters (src/noteflow/) -- **Tauri Desktop Client**: Rust IPC + React UI (client/) +- **Python Backend**: gRPC server, domain logic, infrastructure adapters (`src/noteflow/`) +- **Tauri Desktop Client**: Rust IPC + React UI (`client/`) - **Database**: PostgreSQL with pgvector extension, async SQLAlchemy + asyncpg ## Architecture Pattern @@ -23,29 +23,174 @@ Hexagonal (Ports & Adapters): | `cd client && npm run tauri dev` | Desktop Tauri dev | ## Directory Structure + +### Python Backend (`src/noteflow/`) ``` src/noteflow/ ├── domain/ # Entities, ports, value objects +│ ├── entities/ # Meeting, Segment, Summary, Annotation, NamedEntity, Integration, Project, Processing, SummarizationTemplate, Task, Analytics +│ ├── identity/ # User, Workspace, WorkspaceMembership, roles, context +│ ├── auth/ # OIDC discovery, claims, constants +│ ├── ports/ # Repository protocols +│ │ ├── repositories/ +│ │ │ ├── transcript.py # MeetingRepository, SegmentRepository, SummaryRepository +│ │ │ ├── asset.py # AssetRepository +│ │ │ ├── background.py # DiarizationJobRepository +│ │ │ ├── external/ # WebhookRepository, IntegrationRepository, EntityRepository, UsageRepository +│ │ │ └── identity/ # UserRepository, WorkspaceRepository, ProjectRepository, MembershipRepository, SummarizationTemplateRepository, VoiceProfileRepository +│ │ ├── unit_of_work.py # UnitOfWork protocol (supports_* capability checks) +│ │ ├── async_context.py # Async context utilities +│ │ ├── diarization.py # DiarizationEngine protocol +│ │ ├── ner.py # NEREngine protocol +│ │ ├── gpu.py # GPU detection protocol +│ │ └── calendar.py # CalendarProvider protocol +│ ├── webhooks/ # WebhookEventType, WebhookConfig, WebhookDelivery, payloads +│ ├── triggers/ # Trigger, TriggerAction, TriggerSignal, TriggerProvider +│ ├── summarization/# SummarizationProvider protocol +│ ├── rules/ # Business rules registry, models, builtin rules +│ ├── settings/ # Domain settings base +│ ├── constants/ # Field definitions, placeholders +│ ├── utils/ # time.py (utc_now), validation.py +│ ├── errors.py # Domain-specific exceptions +│ └── value_objects.py ├── application/ # Use-cases/services -├── infrastructure/ # Implementations -├── grpc/ # gRPC layer -├── config/ # Settings -└── cli/ # CLI tools +│ ├── services/ +│ │ ├── meeting/ # MeetingService (CRUD, segments, annotations, summaries, state) +│ │ ├── identity/ # IdentityService (context, workspace, defaults) +│ │ ├── calendar/ # CalendarService (connection, events, oauth, sync) +│ │ ├── summarization/ # SummarizationService, TemplateService, ConsentManager +│ │ ├── project_service/ # CRUD, members, roles, rules, active project +│ │ ├── recovery/ # RecoveryService (meeting, job, audio recovery) +│ │ ├── auth/ # AuthService, workflows, token_exchanger, types, constants +│ │ ├── asr_config/ # ASR configuration service, types, persistence, engine/job managers +│ │ ├── streaming_config/ # Streaming configuration persistence +│ │ ├── analytics/ # AnalyticsService with dashboard data aggregation +│ │ ├── assistant/ # AssistantService for AI chat interactions +│ │ ├── tasks/ # TaskService for action item management +│ │ ├── voice_profile/ # VoiceProfileService for speaker enrollment +│ │ ├── embedding/ # EmbeddingService for vector operations +│ │ ├── export/ # ExportService +│ │ ├── huggingface/ # HfTokenService +│ │ ├── ner/ # NerService +│ │ ├── retention/ # RetentionService +│ │ ├── triggers/ # TriggerService +│ │ ├── webhooks/ # WebhookService +│ │ └── protocols/ # Shared service protocols +│ └── observability/ # Observability ports +├── infrastructure/ # Implementations (see 05-infrastructure-adapters.md) +├── grpc/ # gRPC layer (see 06-grpc-layer.md) +├── cli/ # CLI tools +│ ├── __main__.py # CLI entry point +│ ├── retention.py # Retention management +│ ├── constants.py # CLI constants +│ └── models/ # Model commands (download, status, registry) +└── config/ # Configuration + ├── settings/ # Pydantic settings (_main, _features, _server, _security, _loaders, etc.) + ├── constants/ # Application constants + └── feature_flags.py +``` +### Client Architecture (`client/src/`) +``` client/src/ -├── api/ # API adapters & types -├── hooks/ # Custom React hooks +├── api/ # API layer with adapters +│ ├── adapters/ +│ │ ├── tauri/ # Primary: Tauri IPC adapter with sections +│ │ │ ├── sections/ # Domain-specific API methods +│ │ │ ├── api.ts +│ │ │ ├── stream.ts +│ │ │ └── environment.ts +│ │ ├── mock/ # Development: Simulated responses +│ │ └── cached/ # Fallback: Read-only cache +│ ├── core/ # Connection, reconnection, streams, helpers +│ ├── interfaces/ # Interface definitions +│ ├── types/ # API type definitions +│ │ ├── core.ts, enums.ts, errors.ts +│ │ ├── projects.ts, diagnostics.ts, testing.ts +│ │ └── requests/ # Request type definitions +│ ├── interface.ts # Main NoteFlowAPI interface +│ └── index.ts # API initialization +├── hooks/ # Custom React hooks (domain-organized) +│ ├── audio/ # use-audio-devices, use-asr-config, use-streaming-config +│ ├── auth/ # use-cloud-consent, use-oauth-flow, use-auth-flow, use-oidc-providers, use-huggingface-token +│ ├── data/ # use-async-data, use-guarded-mutation, use-project, use-project-members +│ ├── processing/ # use-diarization, use-entity-extraction, use-post-processing, use-assistant +│ ├── recording/ # use-recording-session, use-recording-app-policy +│ ├── sync/ # use-webhooks, use-calendar-sync, use-integration-sync, use-preferences-sync +│ ├── ui/ # use-toast, use-panel-preferences, use-animated-words, use-recording-panels +│ └── index.ts # Re-exports all hooks ├── contexts/ # React contexts -├── components/ # UI components +│ ├── connection-context.tsx # gRPC connection context +│ ├── workspace-context.tsx # Workspace context +│ ├── project-context.tsx # Project context +│ └── storage.ts # Storage context +├── components/ # React components +│ ├── ui/ # shadcn/ui primitives (40+ components) +│ ├── common/ # Shared components (badges, dialogs, error-boundary, nav-link, stats-card) +│ ├── features/ # Feature-specific components +│ │ ├── entities/ # NER entity highlighting/management +│ │ ├── notes/ # Timestamped notes editor +│ │ └── workspace/ # Workspace switcher +│ ├── layout/ # App layout, sidebar, top-bar +│ ├── settings/ # Settings panel components +│ ├── system/ # System components (tauri event listener, dialogs) +│ ├── dev/ # Development tools +│ └── icons/ # Icon components ├── pages/ # Route pages +│ ├── Home.tsx, Recording.tsx, Meetings.tsx, MeetingDetail.tsx +│ ├── Projects.tsx, ProjectSettings.tsx +│ ├── Settings.tsx, People.tsx, Tasks.tsx, Analytics.tsx +│ ├── NotFound.tsx +│ ├── settings/ # Settings sub-pages (AITab, AudioTab, DiagnosticsTab, IntegrationsTab, StatusTab) +│ └── meeting-detail/ # Meeting detail sub-components └── lib/ # Utilities + ├── config/ # Configuration (server, defaults, app-config, provider-endpoints) + ├── cache/ # Client-side caching + ├── observability/# Logging (client-logs, debug, error-reporting) + ├── system/ # System utilities (events) + ├── preferences.ts, format.ts, utils.ts, time.ts, crypto.ts + └── [other utilities] +``` -client/src-tauri/src/ -├── commands/ # Tauri IPC handlers -├── grpc/ # gRPC client & types -├── state/ # Runtime state -├── audio/ # Audio capture/playback -└── crypto/ # Encryption +### Rust/Tauri Backend (`client/src-tauri/src/`) +``` +src-tauri/src/ +├── commands/ # Tauri IPC command handlers +│ ├── recording/ # capture, device, audio, app_policy, session/ +│ ├── playback/ # audio, events, tick +│ ├── triggers/ # audio, polling +│ ├── audio/ # Audio device commands with helpers +│ ├── meeting.rs, diarization.rs, annotation.rs, export.rs, summary.rs +│ ├── entities.rs, calendar.rs, webhooks.rs, preferences.rs +│ ├── observability.rs, sync.rs, projects.rs, identity.rs, oidc.rs +│ ├── connection.rs, asr.rs, streaming_config.rs, hf_token.rs +│ ├── assistant.rs, analytics.rs, tasks.rs # New Strategy B commands +│ ├── diagnostics.rs, shell.rs, apps.rs, apps_platform.rs +│ └── testing.rs, audio_testing.rs +├── grpc/ # gRPC client +│ ├── client/ # Client implementations by domain +│ │ ├── core.rs, meetings.rs, annotations.rs, diarization.rs +│ │ ├── identity.rs, projects.rs, preferences.rs +│ │ ├── calendar.rs, webhooks.rs, observability.rs +│ │ ├── oidc.rs, sync.rs, asr.rs, streaming.rs, hf_token.rs +│ │ ├── assistant.rs, analytics.rs, tasks.rs # New +│ │ └── converters.rs +│ ├── types/ # Rust type definitions +│ │ ├── core.rs, enums.rs, identity.rs, projects.rs +│ │ ├── preferences.rs, calendar.rs, webhooks.rs, observability.rs +│ │ ├── oidc.rs, sync.rs, results.rs, asr.rs, streaming.rs, hf_token.rs +│ │ └── assistant.rs, analytics.rs, tasks.rs # New +│ ├── streaming/ # Streaming converters and manager +│ └── noteflow.rs # Generated protobuf types +├── state/ # Runtime state management +├── audio/ # Audio capture and playback +├── cache/ # Memory caching +├── crypto/ # Cryptographic operations +├── events/ # Tauri event emission +├── triggers/ # Trigger detection +├── error/ # Error types +├── identity/ # Identity management +└── [config, constants, helpers, main, lib] ``` ## Proto/gRPC Contract diff --git a/.rag/02-domain-entities.md b/.rag/02-domain-entities.md index 958c3d1..26425d1 100644 --- a/.rag/02-domain-entities.md +++ b/.rag/02-domain-entities.md @@ -8,7 +8,7 @@ ### Meeting (`entities/meeting.py`) Aggregate root with lifecycle states. - **Classes**: `MeetingLoadParams`, `MeetingCreateParams`, `Meeting` -- **States**: CREATED → RECORDING → STOPPED → COMPLETED (or ERROR, STOPPING) +- **States**: CREATED -> RECORDING -> STOPPED -> COMPLETED (or ERROR, STOPPING) - **Key Fields**: id (UUID), title, state, project_id, created_at, started_at, stopped_at ### Segment (`entities/segment.py`) @@ -33,28 +33,56 @@ NER extraction results. ### Project (`entities/project.py`) Workspace grouping for meetings. -- **Contains**: `ExportRules`, `TriggerRules` +- **Contains**: `ExportRules`, `TriggerRules`, `EffectiveRules`, `ProjectSettings` - **Key Fields**: id, workspace_id, name, slug, members +- **Helpers**: `slugify()`, `SYSTEM_DEFAULTS` ### Integration (`entities/integration.py`) External service connections. -- **Types**: AUTH, EMAIL, CALENDAR, PKM, CUSTOM -- **Statuses**: DISCONNECTED, CONNECTED, ERROR +- **Types**: AUTH, EMAIL, CALENDAR, PKM, CUSTOM (`IntegrationType`) +- **Statuses**: DISCONNECTED, CONNECTED, ERROR (`IntegrationStatus`) +- **Sync Tracking**: `SyncRun`, `SyncRunStatus` ### SummarizationTemplate (`entities/summarization_template.py`) Configurable summary generation template. - **Key Fields**: id, name, tone, format, verbosity +- **Versioning**: `SummarizationTemplateVersion` + +### Task (`entities/task.py`) +Action items and tasks extracted or created. +- **Key Fields**: id, meeting_id, title, description, status, assignee_id, due_date +- **Statuses**: `TaskStatus` enum + +### ProcessingStatus (`entities/processing.py`) +Post-meeting processing state tracking. +- **Key Fields**: meeting_id, steps, overall_status +- **Related**: `ProcessingStepState`, `ProcessingStepStatus` + +### Analytics Entities (`entities/analytics.py`) +Dashboard and reporting data structures. +- `AnalyticsOverview` - High-level meeting statistics +- `DailyMeetingStats` - Per-day meeting counts and duration +- `EntityAnalytics` - NER entity statistics +- `EntityCategoryStat` - Category breakdowns +- `SpeakerStat` - Speaker participation metrics +- `TopEntity` - Most frequent entities ## Value Objects (`value_objects.py`) ### Type-Safe IDs - `MeetingId = NewType("MeetingId", UUID)` - `AnnotationId = NewType("AnnotationId", UUID)` +- `ProjectId = NewType("ProjectId", UUID)` +- `WorkspaceId = NewType("WorkspaceId", UUID)` +- `UserId = NewType("UserId", UUID)` +- `TemplateId = NewType("TemplateId", UUID)` ### Enums - `MeetingState` (IntEnum): UNSPECIFIED=0, CREATED=1, RECORDING=2, STOPPED=3, COMPLETED=4, ERROR=5, STOPPING=6 - `AnnotationType` (Enum): ACTION_ITEM, DECISION, NOTE, RISK - `ExportFormat` (Enum): MARKDOWN, HTML, PDF +- `Priority` (Enum): LOW, MEDIUM, HIGH +- `TaskStatus` (Enum): PENDING, IN_PROGRESS, COMPLETED, CANCELLED ## Error Hierarchy (`errors.py`) - Base: `DomainError` with `ErrorCode` enum mapping to gRPC `StatusCode` @@ -65,3 +93,16 @@ Configurable summary generation template. - `Workspace`: Tenant container - `WorkspaceMembership`: User-workspace relationship with role - `ProjectRole`: Role definitions (owner, editor, viewer) +- `ProjectMembership`: User-project relationship with role + +## Entity Exports (`__init__.py`) +All entities are re-exported from `domain/entities/__init__.py`: +```python +from noteflow.domain.entities import ( + Meeting, Segment, Summary, Annotation, NamedEntity, + Project, Integration, SummarizationTemplate, Task, + AnalyticsOverview, DailyMeetingStats, EntityAnalytics, + ProcessingStatus, ProcessingStepState, ProcessingStepStatus, + # ... and related types +) +``` diff --git a/.rag/03-domain-ports.md b/.rag/03-domain-ports.md index e60b313..b17d1e8 100644 --- a/.rag/03-domain-ports.md +++ b/.rag/03-domain-ports.md @@ -13,15 +13,23 @@ | `SummaryRepository` | `add()`, `get()`, `list_by_meeting()`, `mark_verified()` | | `AnnotationRepository` | `add()`, `get()`, `list_by_meeting()`, `update()`, `delete()` | -### External Repositories +### Asset Repository (`asset.py`) +| Protocol | Purpose | +|----------|---------| +| `AssetRepository` | Store/retrieve meeting audio files | + +### Background Jobs (`background.py`) +| Protocol | Purpose | +|----------|---------| +| `DiarizationJobRepository` | Track background diarization jobs | + +### External Repositories (`external/`) | Protocol | Location | Purpose | |----------|----------|---------| -| `AssetRepository` | `asset.py` | Store/retrieve meeting audio files | -| `DiarizationJobRepository` | `background.py` | Track background diarization jobs | -| `EntityRepository` | `external/_entity.py` | Persist NER entities | -| `IntegrationRepository` | `external/_integration.py` | Store OAuth integrations | -| `WebhookRepository` | `external/_webhook.py` | Webhook configs and delivery logs | -| `UsageEventRepository` | `external/_usage.py` | Track usage metrics | +| `EntityRepository` | `_entity.py` | Persist NER entities | +| `IntegrationRepository` | `_integration.py` | Store OAuth integrations | +| `WebhookRepository` | `_webhook.py` | Webhook configs and delivery logs | +| `UsageEventRepository` | `_usage.py` | Track usage metrics | ### Identity Repositories (`identity/`) | Protocol | Purpose | @@ -31,6 +39,7 @@ | `ProjectRepository` | Project CRUD and member access | | `ProjectMembershipRepository` | Project role-based access | | `SummarizationTemplateRepository` | Template CRUD and versioning | +| `VoiceProfileRepository` | Speaker voice profile storage | ## Engine/Provider Protocols @@ -45,6 +54,13 @@ Named entity recognition with spaCy - `extract_from_segments(segments: list[Segment]) -> list[NamedEntity]` - `is_ready() -> bool` +### GpuPort (`gpu.py`) +GPU detection and capabilities +- `detect_gpu() -> GpuInfo` +- `is_cuda_available() -> bool` +- `is_rocm_available() -> bool` +- `get_vram_bytes() -> int | None` + ### OAuthPort (`calendar.py`) OAuth PKCE flow - `initiate_auth(provider: str) -> AuthUrl` @@ -63,38 +79,45 @@ LLM summarization ### Hierarchical Protocol Structure ```python UnitOfWorkCapabilities -├── supports_annotations: bool -├── supports_diarization_jobs: bool -├── supports_preferences: bool -├── supports_entities: bool -├── supports_integrations: bool -├── supports_webhooks: bool -├── supports_usage_events: bool -├── supports_users: bool -├── supports_workspaces: bool -├── supports_projects: bool + supports_annotations: bool + supports_diarization_jobs: bool + supports_preferences: bool + supports_entities: bool + supports_integrations: bool + supports_webhooks: bool + supports_usage_events: bool + supports_users: bool + supports_workspaces: bool + supports_projects: bool + supports_tasks: bool + supports_voice_profiles: bool UnitOfWorkCoreRepositories -├── meetings: MeetingRepository -├── segments: SegmentRepository -├── summaries: SummaryRepository -├── assets: AssetRepository + meetings: MeetingRepository + segments: SegmentRepository + summaries: SummaryRepository + assets: AssetRepository UnitOfWorkOptionalRepositories -├── annotations: AnnotationRepository | None -├── diarization_jobs: DiarizationJobRepository | None -├── preferences: PreferencesRepository | None -├── entities: EntityRepository | None -├── integrations: IntegrationRepository | None -├── webhooks: WebhookRepository | None -├── usage_events: UsageEventRepository | None + annotations: AnnotationRepository | None + diarization_jobs: DiarizationJobRepository | None + preferences: PreferencesRepository | None + entities: EntityRepository | None + integrations: IntegrationRepository | None + webhooks: WebhookRepository | None + usage_events: UsageEventRepository | None + tasks: TaskRepository | None + voice_profiles: VoiceProfileRepository | None UnitOfWorkLifecycle -├── __aenter__() / __aexit__() -├── commit() async -├── rollback() async + __aenter__() / __aexit__() + commit() async + rollback() async ``` +## Async Context (`async_context.py`) +Utilities for managing async context in protocols. + ## Key Data Classes ### SummarizationRequest @@ -131,3 +154,13 @@ class CalendarEventInfo: location: str | None description: str | None ``` + +### GpuInfo +```python +@dataclass +class GpuInfo: + available: bool + backend: str | None # "cuda", "rocm", None + device_name: str | None + vram_bytes: int | None +``` diff --git a/.rag/04-application-services.md b/.rag/04-application-services.md index 6d943ff..5e6a5d8 100644 --- a/.rag/04-application-services.md +++ b/.rag/04-application-services.md @@ -9,14 +9,14 @@ Meeting lifecycle, segments, annotations, summaries, state. **Files**: -- `meeting_service.py` — Composite class combining all mixins -- `_base.py` — Core initialization and dependencies -- `_crud_mixin.py` — Create, read, update, delete operations -- `_segments_mixin.py` — Segment management -- `_summaries_mixin.py` — Summary operations -- `_state_mixin.py` — State machine transitions -- `_annotations_mixin.py` — Annotation CRUD -- `_types.py` — Service-specific TypedDicts +- `meeting_service.py` - Composite class combining all mixins +- `_base.py` - Core initialization and dependencies +- `_crud_mixin.py` - Create, read, update, delete operations +- `_segments_mixin.py` - Segment management +- `_summaries_mixin.py` - Summary operations +- `_state_mixin.py` - State machine transitions +- `_annotations_mixin.py` - Annotation CRUD +- `_types.py`, `_meeting_types.py` - Service-specific TypedDicts **Key Methods**: - `create_meeting(title, project_id) -> Meeting` @@ -31,10 +31,10 @@ Meeting lifecycle, segments, annotations, summaries, state. User/workspace context, defaults, tenancy scoping. **Files**: -- `identity_service.py` — Main service -- `_context_mixin.py` — Request context handling -- `_workspace_mixin.py` — Workspace operations -- `_defaults_mixin.py` — Default user/workspace creation +- `identity_service.py` - Main service +- `_context_mixin.py` - Request context handling +- `_workspace_mixin.py` - Workspace operations +- `_defaults_mixin.py` - Default user/workspace creation **Key Methods**: - `get_current_user() -> User` @@ -46,12 +46,13 @@ User/workspace context, defaults, tenancy scoping. OAuth integration, event fetching, sync management. **Files**: -- `calendar_service.py` — Main service -- `_connection_mixin.py` — OAuth connection handling -- `_events_mixin.py` — Event fetching -- `_oauth_mixin.py` — OAuth flow management -- `_service_mixin.py` — Provider configuration -- `_errors.py` — Calendar-specific errors +- `calendar_service.py` - Main service +- `_connection_mixin.py` - OAuth connection handling +- `_events_mixin.py` - Event fetching +- `_oauth_mixin.py` - OAuth flow management +- `_oauth_config_mixin.py` - OAuth client configuration +- `_service_mixin.py` - Provider configuration +- `_errors.py` - Calendar-specific errors **Key Methods**: - `initiate_oauth(provider) -> AuthUrl` @@ -63,9 +64,12 @@ OAuth integration, event fetching, sync management. Summary generation, template management, cloud consent. **Files**: -- `summarization_service.py` — Main service -- `template_service.py` — Template CRUD -- `consent_manager.py` — Cloud consent flow +- `summarization_service.py` - Main service +- `template_service.py` - Template CRUD +- `_consent_manager.py` - Cloud consent flow +- `_template_ops.py` - Template operations +- `_provider_registry.py` - Provider registration +- `_citation_helper.py` - Citation handling **Key Methods**: - `generate_summary(meeting_id, template_id) -> Summary` @@ -79,13 +83,13 @@ Summary generation, template management, cloud consent. Project CRUD, member management, roles, rules. **Files**: -- `__init__.py` — Main service export -- `crud.py` — Project CRUD operations -- `members.py` — Member management -- `roles.py` — Role-based access -- `rules.py` — Project rules configuration -- `active.py` — Active project tracking -- `_types.py` — Service types +- `__init__.py` - Main service export +- `crud.py` - Project CRUD operations +- `members.py` - Member management +- `roles.py` - Role-based access +- `rules.py` - Project rules configuration +- `active.py` - Active project tracking +- `_types.py` - Service types **Key Methods**: - `create_project(name, workspace_id) -> Project` @@ -94,49 +98,158 @@ Project CRUD, member management, roles, rules. - `remove_member(project_id, user_id) -> bool` - `set_active_project(project_id) -> Project` +## New Services (Strategy B) + +### AssistantService (`assistant/`) +AI chat interactions for meeting Q&A. + +**Files**: +- `assistant_service.py` - Main service + +**Key Methods**: +- `ask(request: AssistantRequest) -> AssistantResponse` +- `stream(request: AssistantRequest) -> AsyncIterator[AssistantChunk]` + +### AnalyticsService (`analytics/`) +Dashboard data aggregation and statistics. + +**Files**: +- `service.py` - Main service +- `refresh.py` - Analytics refresh logic + +**Key Methods**: +- `get_overview(workspace_id, date_range) -> AnalyticsOverview` +- `get_speaker_stats(meeting_ids) -> list[SpeakerStat]` +- `get_entity_analytics(workspace_id, date_range) -> EntityAnalytics` + +### TaskService (`tasks/`) +Action item management from meetings. + +**Files**: +- `service.py` - Main service + +**Key Methods**: +- `list_tasks(filters) -> list[Task]` +- `create_task(request) -> Task` +- `update_task(task_id, updates) -> Task` +- `complete_task(task_id) -> Task` + +### VoiceProfileService (`voice_profile/`) +Speaker voice enrollment for improved diarization. + +**Files**: +- `service.py` - Main service + +**Key Methods**: +- `enroll_speaker(audio_data, speaker_name) -> VoiceProfile` +- `get_profile(profile_id) -> VoiceProfile` +- `list_profiles(workspace_id) -> list[VoiceProfile]` + +### EmbeddingService (`embedding/`) +Vector embedding operations for semantic search. + +**Files**: +- `_embedding.py` - Embedding logic + +**Key Methods**: +- `embed_text(text) -> list[float]` +- `embed_segments(segments) -> list[Embedding]` + +## Configuration Services + +### AsrConfigService (`asr_config/`) +ASR model configuration and state management. + +**Files**: +- `service.py` - Main service +- `persistence.py` - Config persistence +- `types.py` - Config types +- `_engine_manager.py` - ASR engine lifecycle +- `_job_manager.py` - Background job management + +**Key Methods**: +- `get_config() -> AsrConfig` +- `update_config(config) -> AsrConfig` +- `get_job_status(job_id) -> AsrJobStatus` + +### StreamingConfigService (`streaming_config/`) +Audio streaming configuration. + +**Files**: +- `persistence.py` - Config persistence + +**Key Methods**: +- `get_config() -> StreamingConfig` +- `update_config(config) -> StreamingConfig` + ## Supporting Services -### NerService (`ner_service.py`) +### NerService (`ner/`) Named entity extraction wrapper, model loading. - `extract_entities(meeting_id) -> list[NamedEntity]` - `is_ready() -> bool` +- `_dedupe.py` - Deduplication logic -### ExportService (`export_service.py`) +### ExportService (`export/`) Transcript export (Markdown, HTML, PDF). - `export(meeting_id, format) -> ExportResult` -### WebhookService (`webhook_service.py`) +### WebhookService (`webhooks/`) Webhook registration, delivery, retry logic. - `register_webhook(config) -> WebhookConfig` - `deliver_event(event) -> WebhookDelivery` -### AuthService (`auth_service.py`) +### AuthService (`auth/`) User authentication, OIDC integration. + +**Files**: +- `service.py` - Main service +- `workflows.py` - Auth flow logic +- `token_exchanger.py` - Token exchange +- `integration_manager.py` - Integration management +- `types.py` - Auth types +- `constants.py` - Auth constants + +**Key Methods**: - `initiate_login(provider) -> AuthUrl` - `complete_login(code, state) -> User` - `logout() -> bool` -### TriggerService (`trigger_service.py`) +### TriggerService (`triggers/`) Calendar/audio/foreground-app trigger detection. - `check_triggers() -> list[TriggerSignal]` - `snooze_triggers(duration) -> bool` -### RetentionService (`retention_service.py`) +### RetentionService (`retention/`) Automatic meeting deletion based on policy. - `apply_retention_policy() -> int` ### RecoveryService (`recovery/`) Data recovery (meeting, job, audio). + +**Files**: +- `recovery_service.py` - Main service +- `_meeting_recoverer.py` - Meeting recovery +- `_job_recoverer.py` - Job recovery +- `_audio_validator.py` - Audio validation + +**Key Methods**: - `recover_meeting(meeting_id) -> Meeting` - `recover_job(job_id) -> DiarizationJob` -### AsrConfigService (`asr_config_service.py`) -ASR model configuration and state. -- `get_config() -> AsrConfig` -- `update_config(config) -> AsrConfig` - -### HfTokenService (`hf_token_service.py`) +### HfTokenService (`huggingface/`) Hugging Face token management. - `set_token(token) -> bool` - `get_status() -> HfTokenStatus` - `validate_token() -> bool` + +## Service Exports (`__init__.py`) +```python +from noteflow.application.services import ( + MeetingService, IdentityService, ProjectService, + SummarizationService, SummarizationTemplateService, + AuthService, ExportService, RetentionService, + TriggerService, RecoveryService, + # ... and related types +) +``` diff --git a/.rag/05-infrastructure-adapters.md b/.rag/05-infrastructure-adapters.md index 730f99e..aa602e7 100644 --- a/.rag/05-infrastructure-adapters.md +++ b/.rag/05-infrastructure-adapters.md @@ -3,6 +3,9 @@ ## Location `src/noteflow/infrastructure/` +## GPU Detection (`gpu/`) +- `detection.py` - GPU availability detection (CUDA, ROCm, MPS) + ## Audio & ASR Layer (`asr/`, `audio/`) ### ASR Engine (`asr/engine.py`) @@ -10,16 +13,37 @@ Faster-whisper wrapper for streaming ASR. - `transcribe(audio: ndarray) -> TranscriptionResult` - `is_ready() -> bool` -### ASR Streaming VAD (`asr/streaming_vad.py`) +### ASR Engines by Backend +- `engine.py` - Default faster-whisper engine +- `pytorch_engine.py` - PyTorch-based engine +- `rocm_engine.py` - ROCm/AMD GPU support +- `factory.py` - Engine factory for backend selection +- `protocols.py` - ASR engine protocol definitions + +### ASR Streaming VAD (`streaming_vad.py`) Voice activity detection for streaming. - `process_audio(chunk: ndarray) -> list[VadSegment]` -### ASR Segmenter (`asr/segmenter/`) +### ASR Segmenter (`segmenter/`) Audio segmentation into speech chunks. +- `segmenter.py` - Main segmenter +- `_types.py` - Segmenter types +- `_constants.py` - Segmenter constants + +### ASR DTOs (`dto.py`) +Data transfer objects for ASR results. ### Audio Capture (`audio/`) Sounddevice capture, ring buffer, VU levels, playback, writer, reader. +## AI Layer (`ai/`) +Shared AI infrastructure. +- `cache.py` - AI response caching +- `constants.py` - AI-related constants +- `_langgraph_compat.py` - LangGraph compatibility utilities +- `adapters/ollama_embedder.py` - Ollama embedding adapter +- `nodes/` - AI processing nodes + ## Diarization Layer (`diarization/`) ### Session Management (`session.py`) @@ -35,29 +59,36 @@ Assign speech segments to speaker IDs. ## Summarization Layer (`summarization/`) -### Cloud Provider (`cloud_provider/`) -Anthropic/OpenAI API integration. -- `generate(request) -> SummarizationResult` +### Provider Implementations +- `cloud_provider/` - Anthropic/OpenAI API integration +- `ollama_provider.py` - Local Ollama LLM +- `mock_provider.py` - Testing provider -### Ollama Provider (`ollama_provider.py`) -Local Ollama LLM. -- `generate(request) -> SummarizationResult` - -### Mock Provider (`mock_provider.py`) -Testing provider. - -### Citation Verifier (`citation_verifier.py`) -Validate summary citations against transcript. -- `verify(summary, segments) -> CitationVerificationResult` - -### Template Renderer (`template_renderer.py`) -Render summary templates. +### Supporting Modules +- `factory.py` - Provider factory for selecting backend +- `template_renderer.py` - Render summary templates +- `citation_verifier.py` - Validate summary citations against transcript +- `_parsing.py` - Response parsing utilities +- `_availability.py` - Provider availability checks +- `_action_items.py` - Action item extraction ## NER Engine (`ner/`) -spaCy-based named entity extraction. +Named entity extraction with pluggable backends. + +### Main Engine (`engine.py`) - `extract(text) -> list[Entity]` - `extract_from_segments(segments) -> list[NamedEntity]` +### Backends (`backends/`) +- `spacy_backend.py` - spaCy transformer-based extraction +- `gliner_backend.py` - GLiNER-based extraction +- `types.py` - Backend type definitions + +### Supporting Modules +- `mapper.py` - Entity type mapping +- `post_processing.py` - Post-extraction processing +- `constants.py` - NER constants + ## Persistence Layer (`persistence/`) ### Database (`database.py`) @@ -67,15 +98,35 @@ Async SQLAlchemy engine, session factory, pgvector support. ### ORM Models (`models/`) ``` -core/ # MeetingModel, SegmentModel, SummaryModel, AnnotationModel -identity/ # UserModel, WorkspaceModel, ProjectModel, MembershipModel +core/ # MeetingModel, SegmentModel, SummaryModel, AnnotationModel, DiarizationJobModel +identity/ # UserModel, WorkspaceModel, ProjectModel, MembershipModel, SettingsModel entities/ # NamedEntityModel, SpeakerModel integrations/ # IntegrationModel, CalendarEventModel, WebhookConfigModel organization/ # SummarizationTemplateModel, TaskModel, TagModel observability/ # UsageEventModel ``` -### Base Repository (`repositories/_base/`) +### Repositories (`repositories/`) +| Repository | Purpose | +|------------|---------| +| `meeting_repo.py` | Meeting persistence | +| `segment_repo.py` | Segment persistence | +| `summary_repo.py` | Summary persistence | +| `annotation_repo.py` | Annotation persistence | +| `entity_repo.py` | Named entity persistence | +| `webhook_repo.py` | Webhook config persistence | +| `preferences_repo.py` | User preferences | +| `asset_repo.py` | Audio asset storage | +| `task_repo.py` | Task persistence | +| `summarization_template_repo.py` | Template persistence | + +### Specialized Repositories +- `diarization_job/` - Job tracking +- `integration/` - OAuth integrations +- `identity/` - User, workspace, project repos +- `usage_event/` - Usage tracking with aggregations + +### Base Repository (`_base/`) ```python class BaseRepository: async def _execute_scalar(query) -> T | None @@ -86,6 +137,12 @@ class BaseRepository: ### Unit of Work (`unit_of_work/`) Transaction management, repository coordination. +### Memory Implementations (`memory/`) +In-memory implementations for testing. + +### Migrations (`migrations/`) +Alembic database migrations. + ## Auth Layer (`auth/`) ### OIDC Discovery (`oidc_discovery.py`) @@ -102,25 +159,35 @@ Pre-configured providers (Google, Outlook, etc.). ## Export Layer (`export/`) -### Markdown Export (`markdown.py`) -Convert transcript to Markdown. +### Format Exporters +- `markdown.py` - Convert transcript to Markdown +- `html.py` - Convert transcript to HTML +- `pdf.py` - Convert transcript to PDF (WeasyPrint) -### HTML Export (`html.py`) -Convert transcript to HTML. - -### PDF Export (`pdf.py`) -Convert transcript to PDF (WeasyPrint). - -## Converters (`converters/`) -Bidirectional conversion between layers: -- ORM ↔ Domain entities -- ASR engine output ↔ Domain entities -- Calendar API ↔ Domain entities -- Webhook payloads ↔ Domain entities -- NER output ↔ Domain entities +### Supporting Modules +- `protocols.py` - Export protocol definitions +- `constants.py` - Export constants +- `_formatting.py` - Formatting helpers ## Calendar Integration (`calendar/`) -Google/Outlook OAuth adapters with event API integration. + +### Provider Adapters +- `google_adapter.py` - Google Calendar integration +- `outlook/` - Outlook Calendar integration + - `outlook_adapter.py` - Main adapter + - `_event_fetcher.py` - Event fetching + - `_event_parser.py` - Event parsing + - `_user_info.py` - User info retrieval + - `_query_builder.py` - API query building + - `_response_limits.py` - Rate limiting + +### OAuth Flow (`oauth/`) +- `oauth_manager.py` - Main OAuth manager +- `oauth_manager_base.py` - Base class +- `_flow_mixin.py` - Flow logic +- `_token_mixin.py` - Token handling +- `_state_mixin.py` - State management +- `_manager_mixin.py` - Manager utilities ## Security (`security/`) @@ -130,24 +197,68 @@ AES-GCM encryption with keyring backend. - `decrypt(data) -> bytes` ### Crypto Utilities (`crypto/`) -Cryptographic helpers. +- `crypto_box.py` - Encryption box +- `_writer.py` - Encrypted file writer +- `_reader.py` - Encrypted file reader +- `_base.py` - Base crypto utilities +- `_binary_io.py` - Binary I/O helpers +- `_constants.py` - Crypto constants + +### Protocols (`protocols.py`) +Security protocol definitions. + +## Converters (`converters/`) +Bidirectional conversion between layers: +- ORM <-> Domain entities +- ASR engine output <-> Domain entities +- Calendar API <-> Domain entities +- Webhook payloads <-> Domain entities +- NER output <-> Domain entities ## Logging & Observability -### Log Buffer (`logging/log_buffer.py`) -In-memory log buffering for client retrieval. +### Logging (`logging/`) +- `events.py` - Structured logging events +- `config.py` - Logging configuration +- `log_buffer.py` - Log buffering +- `processors.py` - Log processors +- `rate_limit.py` - Rate limiting for logs +- `structured.py` - Structured logging utilities +- `timing.py` - Timing utilities +- `transitions.py` - State transition logging ### OpenTelemetry (`observability/otel.py`) Distributed tracing. -### Usage Events (`observability/`) -Track usage metrics. +### Usage Tracking (`observability/usage/`) +- `usage.py` - Main usage tracking +- `_usage_event_builders.py` - Event builders +- `_database_sink.py` - Database sink +- `_logging_sink.py` - Logging sink +- `_otel_sink.py` - OpenTelemetry sink -### Metrics (`metrics/`) -Metric collection utilities. +## Metrics (`metrics/`) +- `collector.py` - Metric collection +- `infrastructure_metrics.py` - Infrastructure metrics +- `memory_logger.py` - Memory logging +- `system_resources.py` - System resource monitoring ## Webhooks (`webhooks/`) ### WebhookExecutor Delivery, signing (HMAC-SHA256), retry with exponential backoff. - `deliver(config, payload) -> WebhookDelivery` + +## Platform (`platform/`) +Platform-specific code and abstractions. + +## Triggers (`triggers/`) +Trigger detection providers: +- `calendar.py` - Calendar proximity triggers +- `audio_activity.py` - Audio activity triggers +- `foreground_app.py` - Foreground app triggers +- `app_audio/` - App-specific audio monitoring (package) + - `app_audio.py` - Main app audio provider + - `_sampler.py` - Audio sampling + - `_window_detection.py` - Window detection + - `_protocols.py` - Protocol definitions diff --git a/.rag/06-grpc-layer.md b/.rag/06-grpc-layer.md index e24166d..94e33d4 100644 --- a/.rag/06-grpc-layer.md +++ b/.rag/06-grpc-layer.md @@ -15,7 +15,7 @@ Python gRPC client wrapper for testing and internal use. Service definition with bidirectional streaming and RPC methods. **Key RPC Groups**: -- **Streaming**: `StreamTranscription(AudioChunk) → TranscriptUpdate` (bidirectional) +- **Streaming**: `StreamTranscription(AudioChunk) -> TranscriptUpdate` (bidirectional) - **Meeting Lifecycle**: CreateMeeting, StopMeeting, ListMeetings, GetMeeting, DeleteMeeting - **Summaries**: GenerateSummary, ListSummarizationTemplates, CreateSummarizationTemplate - **Diarization**: RefineSpeakerDiarization, RenameSpeaker, GetDiarizationJobStatus @@ -24,105 +24,181 @@ Service definition with bidirectional streaming and RPC methods. - **Calendar**: Calendar event sync and OAuth - **Webhooks**: Webhook config and delivery management - **OIDC**: Authentication via OpenID Connect +- **Tasks**: Task CRUD (Strategy B) +- **Analytics**: Dashboard data aggregation (Strategy B) +- **Assistant**: AI chat interactions (Strategy B) -## Server Mixins (`_mixins/`) +## Server Mixins (`mixins/`) ### Streaming Mixin (`streaming/`) Bidirectional ASR streaming. **Files**: -- `_mixin.py` — Main StreamingMixin -- `_session.py` — Stream session lifecycle -- `_asr.py` — ASR engine integration -- `_processing/` — Audio processing pipeline (VAD, chunk tracking, congestion) -- `_partials.py` — Partial transcript handling -- `_cleanup.py` — Resource cleanup +- `_mixin.py` - Main StreamingMixin +- `_session.py` - Stream session lifecycle +- `_session_helpers.py` - Session helper utilities +- `_session_init_helpers.py` - Session initialization +- `_asr.py` - ASR engine integration +- `_metadata.py` - Stream metadata handling +- `_partials.py` - Partial transcript handling +- `_cleanup.py` - Resource cleanup +- `_types.py` - Type definitions +- `_processing/` - Audio processing pipeline + - `_audio_ops.py` - Audio operations + - `_chunk_tracking.py` - Chunk tracking + - `_congestion.py` - Backpressure handling + - `_vad_processing.py` - VAD processing + - `_constants.py`, `_types.py` ### Diarization Mixin (`diarization/`) Speaker diarization (streaming + offline refinement). **Files**: -- `_mixin.py` — Main DiarizationMixin -- `_jobs.py` — Background job management -- `_streaming.py` — Real-time diarization -- `_refinement.py` — Offline refinement with pyannote -- `_speaker.py` — Speaker assignment -- `_status.py` — Job status tracking +- `_mixin.py` - Main DiarizationMixin +- `_jobs.py` - Background job management +- `_job_validation.py` - Job validation +- `_streaming.py` - Real-time diarization +- `_refinement.py` - Offline refinement with pyannote +- `_execution.py` - Job execution +- `_speaker.py` - Speaker assignment +- `_status.py` - Job status tracking +- `_types.py` - Type definitions ### Summarization Mixin (`summarization/`) Summary generation and templates. **Files**: -- `_generation_mixin.py` — Summary generation flow -- `_templates_mixin.py` — Template CRUD -- `_consent_mixin.py` — Cloud consent handling -- `_summary_generation.py` — Core generation logic -- `_template_resolution.py` — Template lookup -- `_context_builders.py` — Context preparation +- `_generation_mixin.py` - Summary generation flow +- `_templates_mixin.py` - Template CRUD mixin +- `_template_crud.py` - Template CRUD operations +- `_consent_mixin.py` - Cloud consent mixin +- `_consent.py` - Consent handling +- `_summary_generation.py` - Core generation logic +- `_template_resolution.py` - Template lookup +- `_context_builders.py` - Context preparation +- `_constants.py` - Summarization constants ### Meeting Mixin (`meeting/`) Meeting lifecycle management. **Files**: -- `meeting_mixin.py` — Meeting state management -- `_project_scope.py` — Project scoping -- `_stop_ops.py` — Stop operations +- `meeting_mixin.py` - Meeting state management +- `_project_scope.py` - Project scoping +- `_stop_ops.py` - Stop operations +- `_post_processing.py` - Post-meeting processing +- `_title_generation.py` - Auto title generation -### Other Mixins -- `project/` — Project management -- `oidc/` — OpenID Connect auth -- `identity/` — User/workspace identity -- `annotation.py` — Segment annotations CRUD -- `export.py` — Export operations -- `entities.py` — Named entity extraction -- `calendar.py` — Calendar sync -- `webhooks.py` — Webhook management -- `preferences.py` — User preferences -- `observability.py` — Usage tracking, metrics -- `sync.py` — State synchronization +### Project Mixin (`project/`) +Project management operations. -### Error Helpers (`errors/`) -- `_abort.py` — `abort_not_found()`, `abort_invalid_argument()` -- `_require.py` — Precondition checks -- `_fetch.py` — Fetch with error handling -- `_parse.py` — Parsing helpers +**Files**: +- `_mixin.py` - Main ProjectMixin +- `_membership.py` - Member management +- `_converters.py` - Project converters + +### OIDC Mixin (`oidc/`) +OpenID Connect authentication. + +**Files**: +- `oidc_mixin.py` - Main OidcMixin +- `_support.py` - OIDC support utilities ### Converters (`converters/`) -Proto ↔ Domain conversion. -- `_domain.py` — Domain entity conversion -- `_timestamps.py` — Timestamp conversion -- `_id_parsing.py` — ID parsing and validation -- `_external.py` — External entity conversion -- `_oidc.py` — OIDC entity conversion +Proto <-> Domain conversion. -## Server Bootstrap +**Files**: +- `_domain.py` - Domain entity conversion +- `_external.py` - External entity conversion +- `_streaming.py` - Streaming type conversion +- `_timestamps.py` - Timestamp conversion +- `_id_parsing.py` - ID parsing and validation +- `_oidc.py` - OIDC entity conversion +- `_export.py` - Export type conversion +- `_task_analytics.py` - Task/analytics conversion -### Files -- `_server_bootstrap.py` — gRPC server creation -- `_startup.py` — Server startup sequence -- `_startup_services.py` — Service initialization -- `_startup_banner.py` — Startup logging -- `_service_shutdown.py` — Graceful shutdown -- `_service_stubs.py` — gRPC stub management +### Error Helpers (`errors/`) +gRPC error handling. -### Interceptors (`interceptors/`) -gRPC interceptors for identity context propagation. +**Files**: +- `_abort.py` - `abort_not_found()`, `abort_invalid_argument()` +- `_require.py` - Precondition checks +- `_fetch.py` - Fetch with error handling +- `_parse.py` - Parsing helpers +- `_webhooks.py` - Webhook error handling +- `_constants.py` - Error constants -## Client Mixins (`_client_mixins/`) +### Other Mixins (Top-level files) +- `annotation.py` - Segment annotations CRUD +- `asr_config.py` - ASR configuration management +- `assistant.py` - AI assistant interactions (Strategy B) +- `analytics_mixin.py` - Analytics data (Strategy B) +- `tasks.py` - Task management (Strategy B) +- `calendar.py` - Calendar sync +- `calendar_oauth_config.py` - OAuth client config +- `entities.py` - Named entity extraction +- `export.py` - Export operations +- `hf_token.py` - HuggingFace token management +- `identity.py` - User/workspace identity +- `observability.py` - Usage tracking, metrics +- `preferences.py` - User preferences +- `streaming_config.py` - Streaming configuration +- `sync.py` - State synchronization +- `webhooks.py` - Webhook management +- `diarization_job.py` - Job status/management + +### Servicer Protocols +- `servicer_core/protocols.py` - Core servicer protocol +- `servicer_other/protocols.py` - Additional protocols +- `protocols.py` - ServicerHost protocol +- `_repository_protocols.py` - Repository protocols +- `_servicer_state.py` - Servicer state management + +### Supporting Modules +- `_audio_processing.py` - Audio utilities +- `_metrics.py` - gRPC metrics +- `_model_status.py` - Model status tracking +- `_processing_status.py` - Processing status +- `_sync_execution.py` - Sync execution helpers +- `_task_callbacks.py` - Task callback handling +- `_types.py` - Shared types + +## Server Bootstrap (`server/`) + +**Files**: +- `__main__.py` - Server entry point +- `__init__.py` - Server exports +- `health.py` - Health check service + +## Startup (`startup/`) +Server startup sequence and service initialization. + +## Client Mixins (`client_mixins/`) Client-side gRPC operations. -- `streaming.py` — Client streaming operations -- `meeting.py` — Meeting CRUD operations -- `diarization.py` — Diarization requests -- `export.py` — Export requests -- `annotation.py` — Annotation operations -- `converters.py` — Response converters -- `protocols.py` — ClientHost protocol +- `streaming.py` - Client streaming operations +- `meeting.py` - Meeting CRUD operations +- `diarization.py` - Diarization requests +- `export.py` - Export requests +- `annotation.py` - Annotation operations +- `converters.py` - Response converters +- `protocols.py` - ClientHost protocol + +## Interceptors (`interceptors/`) +gRPC interceptors for identity context propagation. + +## Types (`types/`) +gRPC-related type definitions. + +## Config (`config/`) +gRPC configuration. + +## Identity (`identity/`) +Identity management for gRPC context. ## Critical Paths ### Recording Flow -1. Client: `StreamTranscription(AudioChunk)` → gRPC streaming +1. Client: `StreamTranscription(AudioChunk)` -> gRPC streaming 2. Server: StreamingMixin consumes chunks 3. ASR Engine: Transcribe via faster-whisper 4. VAD: Segment by silence @@ -131,7 +207,7 @@ Client-side gRPC operations. 7. Client: Receive `TranscriptUpdate` with segments ### Summary Generation Flow -1. Client: `GenerateSummary(meeting_id)` → gRPC call +1. Client: `GenerateSummary(meeting_id)` -> gRPC call 2. SummarizationService: Fetch segments 3. SummarizerProvider: Call LLM 4. Citation Verifier: Validate claims diff --git a/.rag/07-typescript-api-layer.md b/.rag/07-typescript-api-layer.md index fefc408..3b7e88c 100644 --- a/.rag/07-typescript-api-layer.md +++ b/.rag/07-typescript-api-layer.md @@ -5,150 +5,294 @@ ## Architecture Multi-adapter design with fallback chain: -1. **TauriAdapter** (`tauri-adapter.ts`) — Primary: Rust IPC to gRPC -2. **CachedAdapter** (`cached-adapter.ts`) — Fallback: Read-only cache -3. **MockAdapter** (`mock-adapter.ts`) — Development: Simulated responses +1. **TauriAdapter** (`adapters/tauri/`) - Primary: Rust IPC to gRPC +2. **CachedAdapter** (`adapters/cached/`) - Fallback: Read-only cache +3. **MockAdapter** (`adapters/mock/`) - Development: Simulated responses ## API Interface (`interface.ts`) ```typescript interface NoteFlowAPI { // Connection - connect(): Promise; + connect(serverUrl?: string): Promise; disconnect(): Promise; - isConnected(): boolean; - getEffectiveServerUrl(): Promise; + isConnected(): Promise; + getEffectiveServerUrl(): Promise; + getServerInfo(): Promise; + + // Identity (Sprint 16) + getCurrentUser(): Promise; + listWorkspaces(): Promise; + switchWorkspace(workspaceId: string): Promise; + getWorkspaceSettings(request): Promise; + updateWorkspaceSettings(request): Promise; + + // Authentication + initiateAuthLogin(provider, redirectUri?): Promise; + completeAuthLogin(provider, code, state): Promise; + logout(provider?): Promise; + + // Projects (Sprint 18) + createProject(request): Promise; + getProject(request): Promise; + listProjects(request): Promise; + updateProject(request): Promise; + archiveProject(projectId): Promise; + setActiveProject(request): Promise; + addProjectMember(request): Promise; + // ... more project methods // Meetings - createMeeting(request: CreateMeetingRequest): Promise; - getMeeting(request: GetMeetingRequest): Promise; - listMeetings(request: ListMeetingsRequest): Promise; - stopMeeting(request: StopMeetingRequest): Promise; - deleteMeeting(request: DeleteMeetingRequest): Promise; + createMeeting(request): Promise; + getMeeting(request): Promise; + listMeetings(request): Promise; + stopMeeting(meetingId): Promise; + deleteMeeting(meetingId): Promise; // Streaming - startTranscription(meetingId: string): TranscriptionStream; + startTranscription(meetingId): Promise; + getStreamState(): Promise; + resetStreamState(): Promise; - // Diarization - refineSpeakerDiarization(request: RefineDiarizationRequest): Promise; - getDiarizationJobStatus(request: GetJobStatusRequest): Promise; - renameSpeaker(request: RenameSpeakerRequest): Promise; + // Summary & AI + generateSummary(meetingId, forceRegenerate?): Promise; + grantCloudConsent(): Promise; + revokeCloudConsent(): Promise; + getCloudConsentStatus(): Promise; + askAssistant(request): Promise; + streamAssistant(request): Promise; - // Summaries - generateSummary(request: GenerateSummaryRequest): Promise; - listSummarizationTemplates(): Promise; - createSummarizationTemplate(request: CreateTemplateRequest): Promise; + // ASR & Streaming Config + getAsrConfiguration(): Promise; + updateAsrConfiguration(request): Promise; + getStreamingConfiguration(): Promise; + updateStreamingConfiguration(request): Promise; + + // HuggingFace Token + setHuggingFaceToken(request): Promise; + getHuggingFaceTokenStatus(): Promise; + deleteHuggingFaceToken(): Promise; + validateHuggingFaceToken(): Promise; // Annotations - addAnnotation(request: AddAnnotationRequest): Promise; - listAnnotations(request: ListAnnotationsRequest): Promise; - updateAnnotation(request: UpdateAnnotationRequest): Promise; - deleteAnnotation(request: DeleteAnnotationRequest): Promise; + listAnnotations(meetingId, startTime?, endTime?): Promise; + addAnnotation(request): Promise; + updateAnnotation(request): Promise; + deleteAnnotation(annotationId): Promise; // Export - exportTranscript(request: ExportRequest): Promise; + exportTranscript(meetingId, format): Promise; + saveExportFile(content, defaultName, extension): Promise; - // ... 50+ more methods + // Playback (desktop) + startPlayback(meetingId, startTime?): Promise; + pausePlayback(): Promise; + stopPlayback(): Promise; + seekPlayback(position): Promise; + getPlaybackState(): Promise; + + // Diarization + refineSpeakers(meetingId, numSpeakers?): Promise; + getDiarizationJobStatus(jobId): Promise; + renameSpeaker(meetingId, oldSpeakerId, newName): Promise; + cancelDiarization(jobId): Promise; + getActiveDiarizationJobs(): Promise; + + // Audio Devices (desktop) + listAudioDevices(): Promise; + getDefaultAudioDevice(isInput): Promise; + selectAudioDevice(deviceId, isInput): Promise; + listLoopbackDevices(): Promise; + setSystemAudioDevice(deviceId): Promise; + setDualCaptureEnabled(enabled): Promise; + getDualCaptureConfig(): Promise; + + // Triggers (desktop) + setTriggerEnabled(enabled): Promise; + snoozeTriggers(minutes?): Promise; + getTriggerStatus(): Promise; + dismissTrigger(): Promise; + acceptTrigger(title?): Promise; + + // Entities (NER) + extractEntities(meetingId, forceRefresh?): Promise; + updateEntity(meetingId, entityId, text?, category?): Promise; + deleteEntity(meetingId, entityId): Promise; + + // Calendar + listCalendarEvents(hoursAhead?, limit?, provider?): Promise; + getCalendarProviders(): Promise; + initiateCalendarAuth(provider, redirectUri?): Promise; + completeCalendarAuth(provider, code, state): Promise; + getOAuthConnectionStatus(provider): Promise; + disconnectCalendar(provider): Promise; + + // Webhooks + registerWebhook(request): Promise; + listWebhooks(enabledOnly?): Promise; + updateWebhook(request): Promise; + deleteWebhook(webhookId): Promise; + getWebhookDeliveries(webhookId, limit?): Promise; + + // Integration Sync + startIntegrationSync(integrationId): Promise; + getSyncStatus(syncRunId): Promise; + listSyncHistory(integrationId, limit?, offset?): Promise; + getUserIntegrations(): Promise; + + // Observability + getRecentLogs(request?): Promise; + getPerformanceMetrics(request?): Promise; + runConnectionDiagnostics(): Promise; + + // OIDC Provider Management (Sprint 17) + registerOidcProvider(request): Promise; + listOidcProviders(workspaceId?, enabledOnly?): Promise; + getOidcProvider(providerId): Promise; + updateOidcProvider(request): Promise; + deleteOidcProvider(providerId): Promise; + listOidcPresets(): Promise; + + // Tasks (Strategy B) + listTasks(request): Promise; + createTask(request): Promise; + updateTask(request): Promise; + + // Analytics (Strategy B) + getAnalyticsOverview(request): Promise; + listSpeakerStats(request): Promise; + getEntityAnalytics(request): Promise; + + // Summarization Templates + listSummarizationTemplates(request): Promise; + createSummarizationTemplate(request): Promise; + updateSummarizationTemplate(request): Promise; + archiveSummarizationTemplate(request): Promise; + + // Installed Apps (desktop) + listInstalledApps(options?): Promise; + invalidateAppCache(): Promise; + + // Testing (E2E) + checkTestEnvironment(): Promise; + injectTestAudio(meetingId, config): Promise; + injectTestTone(meetingId, frequencyHz, durationSeconds, sampleRate?): Promise; } ``` -## Transcription Streaming +## Adapter Structure -```typescript -interface TranscriptionStream { - send(chunk: AudioChunk): void; - onUpdate(callback: (update: TranscriptUpdate) => void): Promise | void; - onError?(callback: (error: StreamError) => void): void; - onCongestion?(callback: (state: CongestionState) => void): void; - close(): void; -} +### Tauri Adapter (`adapters/tauri/`) +``` +tauri/ +├── api.ts # Main adapter implementation +├── index.ts # Exports +├── stream.ts # Transcription streaming +├── environment.ts # Environment detection +├── constants.ts # Tauri constants +├── types.ts # Adapter-specific types +├── utils.ts # Utility functions +└── sections/ # Domain-specific sections + ├── core.ts + ├── meetings.ts + ├── summarization.ts + ├── diarization.ts + ├── projects.ts + ├── calendar.ts + ├── webhooks.ts + ├── preferences.ts + ├── observability.ts + ├── integrations.ts + ├── entities.ts + ├── exporting.ts + ├── apps.ts + ├── triggers.ts + └── playback.ts ``` -## Connection State (`connection-state.ts`) +### Cached Adapter (`adapters/cached/`) +Read-only offline access with cache TTL. +``` +cached/ +├── index.ts # Main cached adapter +├── base.ts # Base cache utilities +├── defaults.ts # Default values +├── readonly.ts # Read-only rejection helper +├── meetings.ts, projects.ts, annotations.ts, templates.ts +├── webhooks.ts, preferences.ts, diarization.ts +├── playback.ts, streaming.ts, triggers.ts +├── apps.ts, audio.ts, asr.ts, calendar.ts +├── entities.ts, observability.ts, huggingface.ts +``` -```typescript -type ConnectionMode = 'connected' | 'disconnected' | 'cached' | 'mock' | 'reconnecting'; +### Mock Adapter (`adapters/mock/`) +Development/testing with simulated data. +``` +mock/ +├── index.ts # Main mock adapter +├── stream.ts # Mock transcription stream +└── data.ts # Mock data generators +``` -interface ConnectionState { - mode: ConnectionMode; - lastConnectedAt: Date | null; - disconnectedAt: Date | null; - reconnectAttempts: number; - error: string | null; - serverUrl: string | null; -} +## Core Module (`core/`) +``` +core/ +├── connection.ts # Connection state management +├── reconnection.ts # Auto-reconnection logic +├── streams.ts # TranscriptionStream type +├── helpers.ts # Utility helpers +├── constants.ts # API constants +├── errors.ts # Error handling +└── index.ts # Exports ``` ## Type Definitions (`types/`) - -### Core Types (`core.ts`) -- `Meeting`, `FinalSegment`, `WordTiming`, `Summary`, `Annotation` -- `KeyPoint`, `ActionItem`, `Speaker` - -### Enums (`enums.ts`) -- `UpdateType`: partial | final | vad_start | vad_end -- `MeetingState`: created | recording | stopped | completed | error -- `JobStatus`: queued | running | completed | failed | cancelled -- `AnnotationType`: action_item | decision | note | risk -- `ExportFormat`: markdown | html | pdf - -### Feature Types (`features/`) -- `webhooks.ts` — WebhookConfig, WebhookDelivery -- `calendar.ts` — CalendarProvider, CalendarEvent, OAuthConfig -- `ner.ts` — Entity extraction types -- `identity.ts` — User, Workspace -- `oidc.ts` — OIDCProvider, OIDCConfig -- `sync.ts` — SyncStatus, SyncHistory -- `observability.ts` — LogEntry, MetricPoint - -### Projects (`projects.ts`) -- `Project`, `ProjectMember`, `ProjectMembership` - -## Cached Adapter (`cached/`) - -Provides offline read-only access: - -```typescript -// cached/readonly.ts -export function rejectReadOnly(): never { - throw new Error('This action requires an active connection'); -} - -// Pattern in cached adapters -export const cachedMeetings = { - async getMeeting(id: string): Promise { - return meetingCache.get(id) ?? rejectReadOnly(); - }, - async createMeeting(): Promise { - return rejectReadOnly(); // Mutations blocked - }, -}; +``` +types/ +├── core.ts # Core types (Meeting, Segment, Summary, etc.) +├── enums.ts # Enum definitions +├── errors.ts # Error types +├── projects.ts # Project types +├── diagnostics.ts # Diagnostics types +├── testing.ts # Testing types +├── index.ts # Re-exports +└── requests/ # Request type definitions + ├── meetings.ts + ├── annotations.ts + ├── templates.ts + ├── preferences.ts + ├── integrations.ts + ├── oidc.ts + ├── ai.ts + ├── assistant.ts + ├── audio.ts + ├── recording-apps.ts + └── triggers.ts ``` -### Cache Modules -- `meetings.ts` — Meeting cache with TTL -- `projects.ts` — Project cache -- `diarization.ts` — Job status cache -- `annotations.ts` — Annotation cache -- `templates.ts` — Template cache -- `preferences.ts` — Preferences cache - -## API Initialization +## API Initialization (`index.ts`) ```typescript -// api/index.ts +// Auto-initialization on module load export async function initializeAPI(): Promise { try { - const tauriAdapter = await createTauriAdapter(); - return tauriAdapter; + const tauriAPI = await initializeTauriAPI(); + setAPIInstance(tauriAPI); + await tauriAPI.connect(); + setConnectionMode('connected'); + startReconnection(); + return tauriAPI; } catch { - console.warn('Falling back to mock adapter'); - return createMockAdapter(); + // Fall back to mock in browser + setConnectionMode('mock'); + setAPIInstance(mockAPI); + return mockAPI; } } export function getAPI(): NoteFlowAPI { - return window.__NOTEFLOW_API__ ?? mockAdapter; + if (!apiInstance) throw new Error('API not initialized'); + return apiInstance; } ``` @@ -159,7 +303,7 @@ import { getAPI } from '@/api'; const api = getAPI(); const meeting = await api.createMeeting({ title: 'Sprint Planning' }); -const stream = api.startTranscription(meeting.id); +const stream = await api.startTranscription(meeting.id); stream.onUpdate((update) => { if (update.update_type === 'final') { diff --git a/.rag/08-typescript-hooks-contexts.md b/.rag/08-typescript-hooks-contexts.md index 5a18766..ada9b5b 100644 --- a/.rag/08-typescript-hooks-contexts.md +++ b/.rag/08-typescript-hooks-contexts.md @@ -52,22 +52,100 @@ interface ProjectContextValue { } // Usage -const { activeProject, projects, switchProject } = useProjects(); +const { activeProject, projects, switchProject } = useProject(); ``` -## Custom Hooks +### Storage Context (`storage.ts`) +Persistent storage utilities. -### Diarization (`use-diarization.ts`) -Diarization job lifecycle with polling and recovery. +## Hook Organization (Domain Folders) + +### Audio Hooks (`hooks/audio/`) +| Hook | Purpose | +|------|---------| +| `use-audio-devices.ts` | Audio device enumeration and selection | +| `use-audio-devices.helpers.ts` | Device helper functions | +| `use-audio-devices.types.ts` | Device type definitions | +| `use-asr-config.ts` | ASR configuration management | +| `use-streaming-config.ts` | Streaming configuration | +| `use-audio-testing.ts` | Audio testing utilities | ```typescript +// use-audio-devices.ts +interface AudioDevice { + id: string; + name: string; + kind: 'input' | 'output'; +} + +function useAudioDevices(options: UseAudioDevicesOptions): { + devices: AudioDevice[]; + selectedInput: AudioDevice | null; + selectedOutput: AudioDevice | null; + setSelectedInput: (id: string) => void; + setSelectedOutput: (id: string) => void; + isLoading: boolean; +} +``` + +### Auth Hooks (`hooks/auth/`) +| Hook | Purpose | +|------|---------| +| `use-cloud-consent.ts` | Cloud AI consent management | +| `use-oauth-flow.ts` | OAuth authentication flow | +| `use-auth-flow.ts` | General auth flow | +| `use-oidc-providers.ts` | OIDC provider management | +| `use-huggingface-token.ts` | HuggingFace token management | +| `use-secure-integration-secrets.ts` | Secure secret storage | + +```typescript +// use-cloud-consent.ts +function useCloudConsent(): { + hasConsent: boolean; + grantConsent: () => Promise; + revokeConsent: () => Promise; + isLoading: boolean; +} +``` + +### Data Hooks (`hooks/data/`) +| Hook | Purpose | +|------|---------| +| `use-async-data.ts` | Generic async data loading with retry | +| `use-guarded-mutation.ts` | Mutation with offline/permissions guard | +| `use-project.ts` | Project access from context | +| `use-project-members.ts` | Project membership queries | + +```typescript +// use-async-data.ts +const { data, isLoading, error, retry } = useAsyncData( + () => getAPI().getMeeting({ meeting_id: meetingId }), + { + onError: (e) => toast.error(e.message), + deps: [meetingId], + } +); +``` + +### Processing Hooks (`hooks/processing/`) +| Hook | Purpose | +|------|---------| +| `use-diarization.ts` | Diarization job lifecycle with polling | +| `use-entity-extraction.ts` | NER extraction & updates | +| `use-post-processing.ts` | Post-recording processing state | +| `use-assistant.ts` | AI assistant interactions | +| `state.ts` | Processing state management | +| `events.ts` | Processing event handling | + +```typescript +// use-diarization.ts interface UseDiarizationOptions { onComplete?: (status: DiarizationJobStatus) => void; onError?: (error: string) => void; pollInterval?: number; maxRetries?: number; showToasts?: boolean; - autoRecover?: boolean; // Auto-recovery after restart + autoRecover?: boolean; } interface DiarizationState { @@ -89,63 +167,30 @@ function useDiarization(options?: UseDiarizationOptions): { } ``` -### Audio Devices (`use-audio-devices.ts`) -Audio device enumeration and selection. +### Recording Hooks (`hooks/recording/`) +| Hook | Purpose | +|------|---------| +| `use-recording-session.ts` | Recording session management | +| `use-recording-app-policy.ts` | App recording policy detection | -```typescript -interface AudioDevice { - id: string; - name: string; - kind: 'input' | 'output'; -} +### Sync Hooks (`hooks/sync/`) +| Hook | Purpose | +|------|---------| +| `use-webhooks.ts` | Webhook CRUD | +| `use-calendar-sync.ts` | Calendar integration sync | +| `use-integration-sync.ts` | Integration sync state polling | +| `use-integration-validation.ts` | Integration config validation | +| `use-preferences-sync.ts` | Preferences synchronization | +| `use-meeting-reminders.ts` | Meeting reminder notifications | +| `sync-notifications.ts` | Sync notification handling | -function useAudioDevices(options: UseAudioDevicesOptions): { - devices: AudioDevice[]; - selectedInput: AudioDevice | null; - selectedOutput: AudioDevice | null; - setSelectedInput: (id: string) => void; - setSelectedOutput: (id: string) => void; - isLoading: boolean; -} -``` - -### Project Hooks -- `useProject()` — Access project from context -- `useActiveProject()` — Get active project -- `useProjectMembers()` — Project membership queries - -### Cloud Consent (`use-cloud-consent.ts`) -Cloud AI consent state management. - -```typescript -function useCloudConsent(): { - hasConsent: boolean; - grantConsent: () => Promise; - revokeConsent: () => Promise; - isLoading: boolean; -} -``` - -### Integration Hooks -- `useWebhooks()` — Webhook CRUD -- `useEntityExtraction()` — NER extraction & updates -- `useCalendarSync()` — Calendar integration sync -- `useOAuthFlow()` — OAuth authentication flow -- `useAuthFlow()` — General auth flow -- `useOidcProviders()` — OIDC provider management -- `useIntegrationSync()` — Integration sync state polling -- `useIntegrationValidation()` — Integration config validation - -### Recording Hooks -- `useRecordingAppPolicy()` — App recording policy detection -- `usePostProcessing()` — Post-recording processing state - -### Utility Hooks -- `useAsyncData()` — Generic async data loading with retry -- `useGuardedMutation()` — Mutation with offline/permissions guard -- `useToast()` — Toast notifications (shadcn/ui) -- `usePanelPreferences()` — Panel layout preferences -- `useMobile()` — Mobile/responsive detection +### UI Hooks (`hooks/ui/`) +| Hook | Purpose | +|------|---------| +| `use-toast.ts` | Toast notifications (shadcn/ui) | +| `use-panel-preferences.ts` | Panel layout preferences | +| `use-recording-panels.ts` | Recording panel state | +| `use-animated-words.ts` | Word animation for transcription | ## Hook Patterns @@ -169,22 +214,11 @@ useEffect(() => { }, [state.progress]); ``` -### Async Data Loading -```typescript -const { data, isLoading, error, retry } = useAsyncData( - () => getAPI().getMeeting({ meeting_id: meetingId }), - { - onError: (e) => toast.error(e.message), - deps: [meetingId], - } -); -``` - ### Connection-Aware Components ```typescript function MyComponent() { const { isConnected, isReadOnly } = useConnection(); - const { activeProject } = useProjects(); + const { activeProject } = useProject(); if (isReadOnly) { return ; @@ -194,6 +228,19 @@ function MyComponent() { } ``` +### Guarded Mutations +```typescript +const { mutate, isLoading } = useGuardedMutation( + async () => { + await getAPI().deleteMeeting(meetingId); + }, + { + requiresConnection: true, + onError: (e) => toast.error(e.message), + } +); +``` + ## Context Provider Pattern ```typescript @@ -210,3 +257,18 @@ function App() { ); } ``` + +## Hook Re-exports (`index.ts`) + +All hooks are re-exported from domain-specific index files: + +```typescript +// hooks/index.ts +export * from './audio'; +export * from './auth'; +export * from './data'; +export * from './processing'; +export * from './recording'; +export * from './sync'; +export * from './ui'; +``` diff --git a/.rag/09-typescript-components-pages.md b/.rag/09-typescript-components-pages.md index f2bd894..d466a86 100644 --- a/.rag/09-typescript-components-pages.md +++ b/.rag/09-typescript-components-pages.md @@ -6,62 +6,74 @@ ## Component Architecture ### UI Components (`components/ui/`) -30+ shadcn/ui primitives: button, input, dialog, form, toast, etc. +40+ shadcn/ui primitives: button, input, dialog, form, toast, tabs, card, badge, etc. -### Recording Components (`components/recording/`) -| Component | Purpose | +Key UI components: +- `sidebar/` - Sidebar primitives (context, group, layout, menu, primitives) +- `markdown-editor.tsx` - Rich text markdown editor +- `loading-button.tsx` - Button with loading state +- `status-badge.tsx` - Status indicator badge +- `icon-circle.tsx` - Circular icon container +- `confirmation-dialog.tsx` - Generic confirmation dialog + +### Common Components (`components/common/`) +Shared components across the application. + +**Badges** (`badges/`): +- `annotation-type-badge.tsx` - Annotation type display +- `speaker-badge.tsx` - Speaker identification +- `priority-badge.tsx` - Priority indicator + +**Dialogs** (`dialogs/`): +- `confirmation-dialog.tsx` - Reusable confirmation dialog + +**Other**: +- `error-boundary.tsx` - React error boundary +- `empty-state.tsx` - Empty state template +- `stats-card.tsx` - Statistics display card +- `nav-link.tsx` - Navigation link wrapper + +### Feature Components (`components/features/`) + +| Directory | Purpose | |-----------|---------| -| `audio-level-meter.tsx` | Real-time VU meter visualization | -| `confidence-indicator.tsx` | ASR confidence display | -| `vad-indicator.tsx` | Voice activity indicator | -| `buffering-indicator.tsx` | Congestion/buffering display | -| `recording-header.tsx` | Recording session header | -| `stat-card.tsx` | Statistics display | -| `speaker-distribution.tsx` | Speaker time breakdown | -| `idle-state.tsx` | Idle/standby UI | +| `analytics/` | Analytics visualizations | +| `assistant/` | AI assistant chat UI | +| `calendar/` | Calendar integration UI | +| `connectivity/` | Connection status components | +| `entities/` | NER entity highlighting and management | +| `integrations/` | Integration management UI | +| `meetings/` | Meeting list and cards | +| `notes/` | Annotation editor with timestamps | +| `projects/` | Project management UI | +| `recording/` | Recording controls and status | +| `settings/` | Feature settings panels | +| `sync/` | Sync status and controls | +| `tasks/` | Task list and management | +| `workspace/` | Workspace switcher | + +### Layout Components (`components/layout/`) +- `app-layout.tsx` - Main app shell +- `app-sidebar.tsx` - Navigation sidebar +- `top-bar.tsx` - Top navigation bar ### Settings Components (`components/settings/`) -- `developer-options-section.tsx` -- `quick-actions-section.tsx` +- `medium-label.tsx` - Settings label component -### Project Components (`components/projects/`) -- `ProjectScopeFilter.tsx` +### System Components (`components/system/`) +- `tauri-event-listener.tsx` - Tauri event subscription +- `secure-storage-recovery-dialog.tsx` - Storage recovery UI -### Status & Badge Components -| Component | Purpose | -|-----------|---------| -| `entity-highlight.tsx` | NER entity highlighting | -| `entity-management-panel.tsx` | Entity CRUD UI | -| `annotation-type-badge.tsx` | Annotation type display | -| `meeting-state-badge.tsx` | Meeting state indicator | -| `priority-badge.tsx` | Priority indicator | -| `speaker-badge.tsx` | Speaker identification | -| `processing-status.tsx` | Post-processing indicator | -| `api-mode-indicator.tsx` | Connection mode indicator | -| `offline-banner.tsx` | Offline mode warning | +### Development Components (`components/dev/`) +- `dev-profiler.tsx` - Performance profiling +- `simulation-confirmation-dialog.tsx` - Simulation mode confirmation -### Layout Components -- `app-layout.tsx` — Main app shell with sidebar -- `app-sidebar.tsx` — Navigation sidebar -- `error-boundary.tsx` — React error boundary -- `empty-state.tsx` — Empty state template -- `meeting-card.tsx` — Meeting item card -- `NavLink.tsx` — Navigation link wrapper - -### Integration Components -- `calendar-connection-panel.tsx` — Calendar OAuth setup -- `calendar-events-panel.tsx` — Calendar events list -- `integration-config-panel/` — Integration setup wizard - -### Other Components -- `connection-status.tsx` — Connection status display -- `confirmation-dialog.tsx` — Generic confirm dialog -- `timestamped-notes-editor.tsx` — Annotation editor -- `preferences-sync-bridge.tsx` — Preferences sync coordinator -- `preferences-sync-status.tsx` — Sync status display +### Icon Components (`components/icons/`) +- `status-icons.tsx` - Status indicator icons ## Pages (`pages/`) +### Main Pages | Page | Path | Purpose | |------|------|---------| | `Home.tsx` | `/` | Landing/onboarding | @@ -77,11 +89,20 @@ | `NotFound.tsx` | `/*` | 404 fallback | ### Settings Sub-Pages (`pages/settings/`) -- `IntegrationsTab.tsx` — Integration management -- `StatusTab.tsx` — System status +- `AITab.tsx` - AI provider configuration +- `AudioTab.tsx` - Audio device settings +- `DiagnosticsTab.tsx` - System diagnostics +- `IntegrationsTab.tsx` - Integration management +- `StatusTab.tsx` - System status +- `SettingsDialogs.tsx` - Settings modal dialogs ### Meeting Detail Sub-Pages (`pages/meeting-detail/`) -Meeting detail components and sub-views. +- `index.tsx` - Main meeting detail layout +- `header.tsx` - Meeting header with controls +- `transcript-row.tsx` - Individual transcript segment +- `summary-panel.tsx` - AI summary display +- `ask-panel.tsx` - AI assistant chat panel +- `entities-panel.tsx` - NER entities panel ## Page Integration Patterns @@ -89,9 +110,9 @@ Meeting detail components and sub-views. // Recording.tsx example export default function Recording() { const { isConnected, isReadOnly } = useConnection(); - const { activeProject } = useProjects(); + const { activeProject } = useProject(); const { state, start, cancel } = useDiarization(); - const { data: audioDevices } = useAudioDevices(); + const { devices } = useAudioDevices(); // Conditional rendering based on connection state if (isReadOnly) return ; @@ -114,16 +135,31 @@ export default function Recording() { | `` | Wraps app with workspace state | | `` | Wraps app with project state | | `` | Main app shell with sidebar | -| `` | Real-time audio VU meter | -| `` | Recording session metadata | | `` | Inline NER entity highlighting | | `` | Action item/decision/risk badge | -| `` | Meeting state indicator | | `` | Cached/offline mode warning | -| `` | Post-processing progress | -| `` | Connection mode display | +| `` | Generic confirmation modal | +| `` | Empty state placeholder | +| `` | Statistics card | -## Analytics Components (`components/analytics/`) -- `logs-tab.tsx` — Log viewer -- `performance-tab.tsx` — Performance metrics -- `analytics-utils.ts` — Analytics utilities +## Component Organization + +``` +components/ +├── ui/ # shadcn/ui primitives +│ ├── sidebar/ # Sidebar components +│ └── [40+ files] +├── common/ # Shared components +│ ├── badges/ +│ ├── dialogs/ +│ └── [utilities] +├── features/ # Feature-specific +│ ├── entities/ +│ ├── notes/ +│ └── workspace/ +├── layout/ # App layout +├── settings/ # Settings panel +├── system/ # System components +├── dev/ # Dev tools +└── icons/ # Icon components +``` diff --git a/.rag/10-rust-tauri-commands.md b/.rag/10-rust-tauri-commands.md index 6dfb2dd..65ddc08 100644 --- a/.rag/10-rust-tauri-commands.md +++ b/.rag/10-rust-tauri-commands.md @@ -3,7 +3,7 @@ ## Location `client/src-tauri/src/commands/` -## Command Summary (97 Total) +## Command Summary (100+ Total) ### Connection (5) | Command | Purpose | @@ -51,7 +51,21 @@ | `stop_meeting()` | Stop recording | | `delete_meeting()` | Delete meeting | -### Recording (5) — `recording/` +### Recording (`recording/`) +Module structure: +- `mod.rs` - Recording module exports +- `capture.rs` - Native audio capture (cpal) +- `device.rs` - Device resolution utilities +- `dual_capture.rs` - Mic + system audio mixing +- `audio.rs` - Audio utilities +- `app_policy.rs` - Recording app policy +- `stream_state.rs` - VU levels, RMS, counts +- `session/` - Session management + - `mod.rs`, `start.rs`, `stop.rs`, `errors.rs` + - `chunks.rs` - Audio chunk streaming + - `processing.rs` - Audio processing +- `tests.rs` - Recording tests + | Command | Purpose | |---------|---------| | `start_recording()` | Start recording session | @@ -60,15 +74,6 @@ | `get_stream_state()` | Get stream state | | `reset_stream_state()` | Reset stream state | -**Recording Module Files**: -- `session/mod.rs` — Session lifecycle -- `session/chunks/mod.rs` — Audio chunk streaming -- `capture.rs` — Native audio capture (cpal) -- `device.rs` — Device resolution utilities -- `dual_capture.rs` — Mic + system audio mixing -- `stream_state.rs` — VU levels, RMS, counts -- `app_policy.rs` — Recording app policy - ### Annotation (5) | Command | Purpose | |---------|---------| @@ -113,7 +118,7 @@ | `cancel_diarization_job()` | Cancel job | | `get_active_diarization_jobs()` | List active jobs | -### Audio Devices (12) — `audio.rs` +### Audio Devices (`audio.rs`, `audio/helpers.rs`) | Command | Purpose | |---------|---------| | `list_audio_devices()` | List input/output devices | @@ -127,7 +132,12 @@ | `set_audio_mix_levels()` | Set mix levels | | `get_dual_capture_config()` | Get dual capture config | -### Playback (5) — `playback/` +### Playback (`playback/`) +- `mod.rs` - Playback module exports +- `audio.rs` - Audio playback +- `events.rs` - Playback events +- `tick.rs` - Playback timing + | Command | Purpose | |---------|---------| | `start_playback()` | Start audio playback | @@ -144,7 +154,11 @@ | `get_preferences_sync()` | Get sync preferences | | `set_preferences_sync()` | Set sync preferences | -### Triggers (6) — `triggers/` +### Triggers (`triggers/`) +- `mod.rs` - Triggers module exports +- `polling.rs` - Trigger polling +- `audio.rs` - Audio triggers + | Command | Purpose | |---------|---------| | `set_trigger_enabled()` | Enable/disable triggers | @@ -223,7 +237,29 @@ | `list_installed_apps()` | List apps | | `invalidate_app_cache()` | Clear app cache | -### Diagnostics & Testing (5) +### Strategy B Commands (New) + +#### Assistant (`assistant.rs`) +| Command | Purpose | +|---------|---------| +| `ask_assistant()` | Ask AI assistant | +| `stream_assistant()` | Stream assistant response | + +#### Analytics (`analytics.rs`) +| Command | Purpose | +|---------|---------| +| `get_analytics_overview()` | Get dashboard data | +| `list_speaker_stats()` | Get speaker statistics | +| `get_entity_analytics()` | Get entity analytics | + +#### Tasks (`tasks.rs`) +| Command | Purpose | +|---------|---------| +| `list_tasks()` | List tasks | +| `create_task()` | Create task | +| `update_task()` | Update task | + +### Diagnostics & Testing | Command | Purpose | |---------|---------| | `run_connection_diagnostics()` | Run diagnostics | @@ -236,3 +272,43 @@ | Command | Purpose | |---------|---------| | `open_url()` | Open URL in browser | + +## Command Files + +``` +commands/ +├── mod.rs # Module exports +├── connection.rs # Connection commands +├── identity.rs # Identity commands +├── projects.rs # Project commands +├── meeting.rs # Meeting commands +├── recording/ # Recording module +├── playback/ # Playback module +├── annotation.rs # Annotation commands +├── summary.rs # Summary commands +├── export.rs # Export commands +├── entities.rs # Entity commands +├── diarization.rs # Diarization commands +├── audio.rs # Audio device commands +│ └── helpers.rs # Audio helpers +├── preferences.rs # Preferences commands +├── triggers/ # Triggers module +├── calendar.rs # Calendar commands +├── webhooks.rs # Webhook commands +├── oidc.rs # OIDC commands +├── sync.rs # Sync commands +├── observability.rs # Observability commands +├── asr.rs # ASR config commands +├── streaming_config.rs # Streaming commands +├── hf_token.rs # HuggingFace commands +├── apps.rs # Apps commands +├── apps_platform.rs # Platform-specific apps +├── assistant.rs # Assistant commands (Strategy B) +├── analytics.rs # Analytics commands (Strategy B) +├── tasks.rs # Task commands (Strategy B) +├── diagnostics.rs # Diagnostics commands +├── shell.rs # Shell commands +├── testing.rs # Testing commands +├── audio_testing.rs # Audio testing commands +└── *_tests.rs # Test files +``` diff --git a/.rag/11-rust-grpc-types.md b/.rag/11-rust-grpc-types.md index 4afd07e..4cccae9 100644 --- a/.rag/11-rust-grpc-types.md +++ b/.rag/11-rust-grpc-types.md @@ -131,6 +131,11 @@ pub enum Priority { - `results.rs` — Result wrappers - `hf_token.rs` — HuggingFace types +### Strategy B Type Modules (New) +- `analytics.rs` — Analytics overview, speaker stats, entity analytics +- `assistant.rs` — AI assistant request/response types +- `tasks.rs` — Task types with status enum + ## Client Modules (`grpc/client/`) | File | Purpose | diff --git a/.rag/12-rust-state-audio-crypto.md b/.rag/12-rust-state-audio-crypto.md index 51013f1..1ab8707 100644 --- a/.rag/12-rust-state-audio-crypto.md +++ b/.rag/12-rust-state-audio-crypto.md @@ -83,6 +83,13 @@ pub struct AudioConfig { } ``` +### Additional State Modules +- `shutdown.rs` — Graceful shutdown handling +- `recording_types.rs` — Recording type definitions +- `status.rs` — Status tracking +- `trigger_types.rs` — Trigger type definitions +``` + ## Audio Handling (`audio/`) ### Audio Capture (`capture.rs`) @@ -147,8 +154,10 @@ pub struct PlaybackStarted { ### Utilities - `mixer.rs` — Dual-capture audio mixing +- `mixer_helpers.rs` — Mixer helper functions - `loader.rs` — Audio file loading - `windows_loopback.rs` — Windows system audio capture +- `drift_compensation/` — Clock drift correction for dual capture ## Encryption (`crypto/`) diff --git a/.rag/13-common-utilities.md b/.rag/13-common-utilities.md index d4af528..3f8bcfa 100644 --- a/.rag/13-common-utilities.md +++ b/.rag/13-common-utilities.md @@ -90,9 +90,23 @@ const unsubscribe = preferences.subscribe((prefs) => { - `tauri-events.ts` — Tauri event subscriptions - `event-emitter.ts` — Generic event emitter -### Other Utilities -- `utils.ts` — Generic TypeScript utilities -- `object-utils.ts` — Object manipulation +### Utilities (`utils/`) +- `async.ts` — Async/await utilities +- `download.ts` — File download helpers +- `event-emitter.ts` — Generic event emitter +- `format.ts` — Time, duration, GB, percentages +- `id.ts` — ID generation utilities +- `object.ts` — Object manipulation +- `polling.ts` — Polling utilities +- `time.ts` — Time constants + +### State Management (`state/`) +- `entities.ts` — Entity state management + +### System Utilities (`system/`) +- `events.ts` — System event handling + +### Other Utilities (Root Level) - `speaker-utils.ts` — Speaker ID formatting - `integration-utils.ts` — Integration helpers - `entity-store.ts` — Entity caching for NER diff --git a/.rag/14-testing.md b/.rag/14-testing.md new file mode 100644 index 0000000..dd20c5e --- /dev/null +++ b/.rag/14-testing.md @@ -0,0 +1,233 @@ +# NoteFlow Testing Conventions + +## Location +- Python: `tests/` (pytest) +- TypeScript: `client/src/**/*.test.ts` (Vitest) +- Rust: `client/src-tauri/src/**/*_tests.rs` (cargo test) +- E2E: `client/e2e/` (Playwright) + +## Python Testing + +### Running Tests +```bash +pytest # Full suite +pytest -m "not integration" # Skip external-service tests +pytest tests/domain/ # Run specific directory +pytest -k "test_segment" # Run by pattern +pytest tests/quality/ # Quality gates +``` + +### Markers +| Marker | Purpose | +|--------|---------| +| `@pytest.mark.slow` | Model loading tests | +| `@pytest.mark.integration` | External service tests | +| `@pytest.mark.asyncio` | Async tests (auto-mode enabled) | + +### Test Structure +- Test files: `test_*.py` +- Test functions: `test_*` +- Fixtures in `tests/conftest.py` + +### Quality Gates (`tests/quality/`) + +**Run after any non-trivial changes:** +```bash +pytest tests/quality/ +``` + +| Test File | Enforces | +|-----------|----------| +| `test_test_smells.py` | No assertion roulette, no conditional logic, no loops in tests | +| `test_magic_values.py` | No magic numbers in assignments | +| `test_code_smells.py` | Code quality checks | +| `test_duplicate_code.py` | No duplicate code patterns | +| `test_stale_code.py` | No stale/dead code | +| `test_decentralized_helpers.py` | Helpers consolidated properly | +| `test_unnecessary_wrappers.py` | No unnecessary wrapper functions | +| `test_baseline_self.py` | Baseline validation self-checks | + +### Forbidden Patterns in Tests +- Loops (`for`, `while`) — Use `@pytest.mark.parametrize` +- Conditionals (`if`) — Use `@pytest.mark.parametrize` +- Multiple assertions without messages — Add assertion messages +- Empty catch blocks — All errors must be logged/handled + +### Global Fixtures (DO NOT REDEFINE) +| Fixture | Description | +|---------|-------------| +| `reset_context_vars` | Reset logging context variables | +| `mock_uow` | Mock Unit of Work | +| `crypto` | Crypto utilities | +| `meetings_dir` | Temporary meetings directory | +| `webhook_config` | Single-event webhook config | +| `webhook_config_all_events` | All-events webhook config | +| `sample_datetime` | Sample datetime | +| `calendar_settings` | Calendar settings | +| `meeting_id` | Sample meeting ID | +| `sample_meeting` | Sample Meeting entity | +| `recording_meeting` | Recording-state Meeting | +| `sample_rate` | Audio sample rate | +| `mock_grpc_context` | Mock gRPC context | +| `mockasr_engine` | Mock ASR engine | +| `mock_optional_extras` | Mock optional extras | +| `mock_oauth_manager` | Mock OAuth manager | +| `memory_servicer` | In-memory servicer | +| `approx_float` | Approximate float comparison | +| `approx_sequence` | Approximate sequence comparison | + +## TypeScript Testing (Vitest) + +### Running Tests +```bash +cd client +npm run test # Run all tests +npm run test:watch # Watch mode +npm run test:coverage # With coverage +``` + +### Test Files +- Unit tests: `*.test.ts` or `*.test.tsx` +- Located next to source files + +### Patterns +```typescript +import { describe, it, expect, vi } from 'vitest'; + +describe('MyComponent', () => { + it('should render correctly', () => { + // Arrange + const props = { title: 'Test' }; + + // Act + render(); + + // Assert + expect(screen.getByText('Test')).toBeInTheDocument(); + }); +}); +``` + +## Rust Testing (cargo test) + +### Running Tests +```bash +cd client/src-tauri +cargo test # All tests +cargo test -- --nocapture # With output +``` + +### Test Files +- Test modules: `*_tests.rs` +- Unit tests: `#[cfg(test)]` modules in source files + +### Patterns +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_audio_capture() { + // Arrange + let config = CaptureConfig::default(); + + // Act + let result = AudioCapture::new(config); + + // Assert + assert!(result.is_ok()); + } +} +``` + +## E2E Testing (Playwright) + +### Running Tests +```bash +cd client +npm run test:e2e # Run all E2E tests +npm run test:e2e:ui # With UI mode +``` + +### Requirements +- Frontend must be running on `:5173` +- Backend server must be running + +### Test Files +- Location: `client/e2e/` +- Pattern: `*.spec.ts` + +## Makefile Quality Commands + +```bash +make quality # ALL quality checks (TS + Rust + Python) +make quality-py # Python: lint + type-check + test-quality +make quality-ts # TypeScript: type-check + lint + test-quality +make quality-rs # Rust: clippy + lint + +make e2e # Playwright tests +make e2e-ui # Playwright with UI +make e2e-grpc # Rust gRPC integration tests +``` + +## Test AAA Pattern + +All tests should follow **Arrange-Act-Assert**: + +```python +def test_create_meeting(): + # Arrange + service = MeetingService(mock_uow) + params = MeetingCreateParams(title="Sprint Planning") + + # Act + meeting = await service.create_meeting(params) + + # Assert + assert meeting.title == "Sprint Planning" + assert meeting.state == MeetingState.CREATED +``` + +## Integration Tests + +### PostgreSQL (testcontainers) +```python +@pytest.fixture +async def postgres_uow(): + async with PostgresContainer() as postgres: + engine = create_async_engine(postgres.get_connection_url()) + yield SQLAlchemyUnitOfWork(engine) +``` + +### gRPC Server +```python +@pytest.fixture +async def grpc_server(): + server = await create_test_server() + yield server + await server.stop(grace=1.0) +``` + +## Coverage Goals + +| Area | Target | +|------|--------| +| Critical business logic | 100% | +| Domain entities | 90%+ | +| Application services | 85%+ | +| Infrastructure | 70%+ | +| gRPC mixins | 80%+ | + +## Forbidden in Quality Tests + +**NEVER modify without explicit permission:** +- Adding entries to allowlists/baselines +- Increasing thresholds +- Adding exclusion patterns +- Modifying filter functions + +**When quality tests fail:** +1. Fix the actual code (not the test) +2. If false positive: improve detection logic +3. NEVER add arbitrary values to allowlists diff --git a/README.md b/README.md index c2df106..d12822a 100644 --- a/README.md +++ b/README.md @@ -50,34 +50,47 @@ The Tauri desktop app requires a working Rust toolchain. ## Container-based development -The repository includes a `compose.yaml` with a server container (and a commented-out Postgres service). +The repository includes two compose files: +- `docker-compose.prod.yml` — Production deployment with prebuilt images +- `docker-compose.dev.yml` — Development with hot reload and all extras -### Option A: Run the server in Docker, clients locally - -1) Create a `.env` file from `example.env` and set any needed settings. -2) Start the server container: +### Production (prebuilt images) ```bash -docker compose up -d server +# CPU server (default) +docker compose -f docker-compose.prod.yml up -d + +# NVIDIA GPU server +docker compose -f docker-compose.prod.yml --profile gpu up -d + +# AMD ROCm GPU server +docker compose -f docker-compose.prod.yml --profile rocm up -d ``` -The server will expose `50051` on the host; point your client at `localhost:50051`. - -### Option B: Enable PostgreSQL in Docker - -`compose.yaml` includes a commented `db` service using `pgvector/pgvector:pg15`. To use it: - -1) Uncomment the `db` service and `depends_on`/`environment` lines in `compose.yaml`. -2) Set `NOTEFLOW_DATABASE_URL` to the container URL (example): - -``` -postgresql+asyncpg://noteflow:noteflow@db:5432/noteflow -``` - -3) Start services: +### Development (with hot reload) ```bash -docker compose up -d +# CPU dev server + frontend +docker compose -f docker-compose.dev.yml up -d + +# NVIDIA GPU dev server +docker compose -f docker-compose.dev.yml --profile gpu up -d + +# AMD ROCm dev server +docker compose -f docker-compose.dev.yml --profile rocm up -d +``` + +### Building images + +```bash +# Build all production images +docker buildx bake prod + +# Build and push to registry +REGISTRY=git.baked.rocks/vasceannie docker buildx bake --push prod + +# Build dev images locally +docker buildx bake dev ``` ## Common commands diff --git a/compose.gpu.yaml b/compose.gpu.yaml deleted file mode 100644 index 91bea33..0000000 --- a/compose.gpu.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# GPU-specific overrides for Docker Compose -# Usage: docker compose -f compose.yaml -f compose.gpu.yaml --profile server-gpu up -# -# This file provides additional NVIDIA GPU configuration options. -# It is optional - the server-gpu profile in compose.yaml includes sensible defaults. - -services: - server-gpu: - # Override GPU allocation (uncomment to customize) - deploy: - resources: - reservations: - devices: - - driver: nvidia - # Use 'all' to use all available GPUs, or specify count - count: 1 - # count: all - capabilities: [gpu] - # Optionally specify device IDs (e.g., for multi-GPU systems) - # device_ids: ['0'] - - # Additional environment variables for GPU optimization - environment: - # PyTorch CUDA settings - CUDA_VISIBLE_DEVICES: "0" - # Enable TF32 for better performance on Ampere+ GPUs - NVIDIA_TF32_OVERRIDE: "1" - # Memory management - PYTORCH_CUDA_ALLOC_CONF: "expandable_segments:True" - - # AMD ROCm-specific overrides (optional) - server-rocm: - environment: - HIP_VISIBLE_DEVICES: "0" - HSA_OVERRIDE_GFX_VERSION: "" - AMD_LOG_LEVEL: "1" diff --git a/compose.yaml b/compose.yaml.deprecated similarity index 100% rename from compose.yaml rename to compose.yaml.deprecated diff --git a/docker-bake.hcl b/docker-bake.hcl index 09c5890..d22f3a7 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -2,30 +2,27 @@ # Docker Buildx Bake configuration for NoteFlow # # Usage: -# docker buildx bake # Build default targets +# docker buildx bake # Build default targets (CPU server) +# docker buildx bake prod # Build all production images +# docker buildx bake dev # Build all dev images # docker buildx bake server # Build CPU server only -# docker buildx bake server-gpu # Build GPU server only +# docker buildx bake server-gpu # Build CUDA GPU server only # docker buildx bake server-rocm # Build ROCm GPU server only -# docker buildx bake server-rocm-dev # Build ROCm GPU dev server (hot reload) -# docker buildx bake servers # Build all server variants (parallel) -# docker buildx bake servers-gpu # Build GPU variants only (CUDA + ROCm) -# docker buildx bake client # Build client targets -# docker buildx bake all # Build everything # docker buildx bake --print # Show build plan without building +# docker buildx bake --push prod # Build and push production images # -# With specific options: -# docker buildx bake server --set server.tags=myregistry/noteflow:v1.0 -# docker buildx bake --push all # Build and push all images +# Registry configuration: +# REGISTRY=git.baked.rocks/vasceannie docker buildx bake --push prod # ============================================================================= # Variables # ============================================================================= variable "REGISTRY" { - default = "" + default = "git.baked.rocks/vasceannie" } -variable "IMAGE_PREFIX" { +variable "IMAGE_NAME" { default = "noteflow" } @@ -54,60 +51,85 @@ variable "SPACY_MODEL_URL" { } # ============================================================================= -# Functions +# Functions - Single repo tagging scheme # ============================================================================= -function "tag" { - params = [name] - result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-${name}:${TAG}" : "${IMAGE_PREFIX}-${name}:${TAG}" +# Base image path: REGISTRY/IMAGE_NAME +function "image_base" { + params = [] + result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_NAME}" : IMAGE_NAME } -function "tags" { - params = [name] - result = [ - tag(name), - REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-${name}:latest" : "${IMAGE_PREFIX}-${name}:latest" +# CPU server tags: :TAG and :latest +function "tags_cpu" { + params = [] + result = TAG == "latest" ? [ + "${image_base()}:latest" + ] : [ + "${image_base()}:${TAG}", + "${image_base()}:latest" ] } +# GPU (CUDA) server tags: :TAG-gpu and :latest-gpu +function "tags_gpu" { + params = [] + result = TAG == "latest" ? [ + "${image_base()}:latest-gpu" + ] : [ + "${image_base()}:${TAG}-gpu", + "${image_base()}:latest-gpu" + ] +} + +# ROCm server tags: :TAG-rocm and :latest-rocm +function "tags_rocm" { + params = [] + result = TAG == "latest" ? [ + "${image_base()}:latest-rocm" + ] : [ + "${image_base()}:${TAG}-rocm", + "${image_base()}:latest-rocm" + ] +} + +# Dev tags (not published, local only) +function "tags_dev" { + params = [suffix] + result = ["${image_base()}:dev${suffix}"] +} + # ============================================================================= -# Groups - Enable parallel builds +# Groups - Organized by use case # ============================================================================= group "default" { targets = ["server"] } +# Production images (publishable) +group "prod" { + targets = ["server", "server-gpu", "server-rocm"] +} + +# Development images (local only) +group "dev" { + targets = ["server-dev", "server-gpu-dev", "server-rocm-dev"] +} + +# All server variants group "servers" { targets = ["server", "server-gpu", "server-rocm"] } +# GPU variants only group "servers-gpu" { targets = ["server-gpu", "server-rocm"] } -group "servers-full" { - targets = ["server", "server-full", "server-gpu", "server-gpu-full", "server-rocm", "server-rocm-full"] -} - -group "client" { - targets = ["client-build", "client-dev"] -} - -group "all" { - targets = [ - "server", - "server-full", - "server-gpu", - "server-gpu-full", - "server-rocm", - "server-rocm-full", - "client-build" - ] -} - +# CI targets with GHA caching group "ci" { - targets = ["server", "server-gpu", "server-rocm", "client-build"] + targets = ["server-ci", "server-gpu-ci", "server-rocm-ci"] } # ============================================================================= @@ -130,7 +152,7 @@ target "_server-common" { SPACY_MODEL_URL = SPACY_MODEL_URL } cache-from = [ - "type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server:cache" : "${IMAGE_PREFIX}-server:cache"}" + "type=registry,ref=${image_base()}:cache" ] cache-to = [ "type=inline" @@ -146,7 +168,7 @@ target "_server-gpu-common" { SPACY_MODEL_URL = SPACY_MODEL_URL } cache-from = [ - "type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server-gpu:cache" : "${IMAGE_PREFIX}-server-gpu:cache"}" + "type=registry,ref=${image_base()}:cache-gpu" ] cache-to = [ "type=inline" @@ -162,18 +184,7 @@ target "_server-rocm-common" { SPACY_MODEL_URL = SPACY_MODEL_URL } cache-from = [ - "type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server-rocm:cache" : "${IMAGE_PREFIX}-server-rocm:cache"}" - ] - cache-to = [ - "type=inline" - ] -} - -target "_client-common" { - inherits = ["_common"] - dockerfile = "docker/client.Dockerfile" - cache-from = [ - "type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-client:cache" : "${IMAGE_PREFIX}-client:cache"}" + "type=registry,ref=${image_base()}:cache-rocm" ] cache-to = [ "type=inline" @@ -181,58 +192,27 @@ target "_client-common" { } # ============================================================================= -# Server Targets (CPU) +# Production Server Targets # ============================================================================= +# CPU server (multi-arch: amd64 + arm64) target "server" { inherits = ["_server-common"] target = "server" - tags = tags("server") + tags = tags_cpu() + platforms = ["linux/amd64", "linux/arm64"] labels = { - "org.opencontainers.image.title" = "NoteFlow Server (CPU)" - "org.opencontainers.image.description" = "NoteFlow gRPC server - CPU-only build" + "org.opencontainers.image.title" = "NoteFlow Server" + "org.opencontainers.image.description" = "NoteFlow gRPC server - CPU multi-arch build" } } -target "server-full" { - inherits = ["_server-common"] - target = "server-full" - tags = tags("server-full") - labels = { - "org.opencontainers.image.title" = "NoteFlow Server Full (CPU)" - "org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - CPU-only build" - } -} - -target "server-dev" { - inherits = ["_server-common"] - target = "dev" - tags = tags("server-dev") - labels = { - "org.opencontainers.image.title" = "NoteFlow Server Dev (CPU)" - "org.opencontainers.image.description" = "NoteFlow development server - CPU-only build" - } -} - -target "server-ner" { - inherits = ["_server-common"] - target = "with-ner" - tags = tags("server-ner") - labels = { - "org.opencontainers.image.title" = "NoteFlow Server with NER (CPU)" - "org.opencontainers.image.description" = "NoteFlow server with spaCy NER - CPU-only build" - } -} - -# ============================================================================= -# Server Targets (GPU - NVIDIA CUDA) -# ============================================================================= - +# CUDA GPU server (amd64 only) target "server-gpu" { inherits = ["_server-gpu-common"] target = "server" - tags = tags("server-gpu") - platforms = ["linux/amd64"] # GPU images are x86_64 only + tags = tags_gpu() + platforms = ["linux/amd64"] labels = { "org.opencontainers.image.title" = "NoteFlow Server (GPU)" "org.opencontainers.image.description" = "NoteFlow gRPC server - NVIDIA CUDA GPU build" @@ -240,26 +220,11 @@ target "server-gpu" { } } -target "server-gpu-full" { - inherits = ["_server-gpu-common"] - target = "server-full" - tags = tags("server-gpu-full") - platforms = ["linux/amd64"] - labels = { - "org.opencontainers.image.title" = "NoteFlow Server Full (GPU)" - "org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - NVIDIA CUDA GPU build" - "ai.noteflow.cuda.version" = CUDA_VERSION - } -} - -# ============================================================================= -# Server Targets (GPU - AMD ROCm) -# ============================================================================= - +# ROCm GPU server (amd64 only) target "server-rocm" { inherits = ["_server-rocm-common"] target = "server" - tags = tags("server-rocm") + tags = tags_rocm() platforms = ["linux/amd64"] labels = { "org.opencontainers.image.title" = "NoteFlow Server (ROCm)" @@ -268,22 +233,39 @@ target "server-rocm" { } } -target "server-rocm-full" { - inherits = ["_server-rocm-common"] - target = "server-full" - tags = tags("server-rocm-full") - platforms = ["linux/amd64"] +# ============================================================================= +# Development Server Targets (local only, not published) +# ============================================================================= + +# CPU dev server +target "server-dev" { + inherits = ["_server-common"] + target = "dev" + tags = tags_dev("") labels = { - "org.opencontainers.image.title" = "NoteFlow Server Full (ROCm)" - "org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - AMD ROCm GPU build" - "ai.noteflow.rocm.version" = ROCM_VERSION + "org.opencontainers.image.title" = "NoteFlow Server Dev" + "org.opencontainers.image.description" = "NoteFlow development server with all extras" } } +# CUDA GPU dev server +target "server-gpu-dev" { + inherits = ["_server-gpu-common"] + target = "dev" + tags = tags_dev("-gpu") + platforms = ["linux/amd64"] + labels = { + "org.opencontainers.image.title" = "NoteFlow Server Dev (GPU)" + "org.opencontainers.image.description" = "NoteFlow development server - NVIDIA CUDA GPU build" + "ai.noteflow.cuda.version" = CUDA_VERSION + } +} + +# ROCm GPU dev server target "server-rocm-dev" { inherits = ["_server-rocm-common"] - target = "server-dev" - tags = tags("server-rocm-dev") + target = "dev" + tags = tags_dev("-rocm") platforms = ["linux/amd64"] labels = { "org.opencontainers.image.title" = "NoteFlow Server Dev (ROCm)" @@ -293,65 +275,23 @@ target "server-rocm-dev" { } # ============================================================================= -# Client Targets -# ============================================================================= - -target "client-build" { - inherits = ["_client-common"] - target = "client-build" - tags = tags("client") - labels = { - "org.opencontainers.image.title" = "NoteFlow Client Build" - "org.opencontainers.image.description" = "NoteFlow Tauri desktop client build" - } -} - -target "client-dev" { - inherits = ["_client-common"] - target = "client-dev" - tags = tags("client-dev") - labels = { - "org.opencontainers.image.title" = "NoteFlow Client Dev" - "org.opencontainers.image.description" = "NoteFlow Tauri client development environment" - } -} - -# ============================================================================= -# Multi-Platform Targets (for CPU images) -# ============================================================================= - -target "server-multiplatform" { - inherits = ["server"] - platforms = ["linux/amd64", "linux/arm64"] - tags = tags("server-multiplatform") -} - -target "server-full-multiplatform" { - inherits = ["server-full"] - platforms = ["linux/amd64", "linux/arm64"] - tags = tags("server-full-multiplatform") -} - -# ============================================================================= -# CI/CD Specific Targets +# CI/CD Targets (GHA caching) # ============================================================================= target "server-ci" { inherits = ["server"] - cache-from = [ - "type=gha" - ] - cache-to = [ - "type=gha,mode=max" - ] + cache-from = ["type=gha"] + cache-to = ["type=gha,mode=max"] } target "server-gpu-ci" { inherits = ["server-gpu"] - cache-from = [ - "type=gha" - ] - cache-to = [ - "type=gha,mode=max" - ] + cache-from = ["type=gha"] + cache-to = ["type=gha,mode=max"] +} + +target "server-rocm-ci" { + inherits = ["server-rocm"] + cache-from = ["type=gha"] + cache-to = ["type=gha,mode=max"] } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f499881 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,207 @@ +services: + db: + container_name: noteflow-postgres + image: pgvector/pgvector:pg15 + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-noteflow} + POSTGRES_USER: ${POSTGRES_USER:-noteflow} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-noteflow} + volumes: + - noteflow_pg_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-noteflow} -d ${POSTGRES_DB:-noteflow}"] + interval: 5s + timeout: 5s + retries: 10 + networks: + - noteflow-net + + redis: + container_name: noteflow-redis + image: redis:7-alpine + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - noteflow_redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 10 + networks: + - noteflow-net + + qdrant: + container_name: noteflow-qdrant + image: qdrant/qdrant:v1.12.1 + restart: unless-stopped + ports: + - "6333:6333" + - "6334:6334" + volumes: + - noteflow_qdrant_data:/qdrant/storage + environment: + QDRANT__SERVICE__GRPC_PORT: 6334 + healthcheck: + test: ["CMD-SHELL", "bash -c '/dev/null | head -5 || echo "cuDNN libs not found (will be installed on first run)" -echo "======================" - -# Run uv sync to ensure dependencies are installed -uv sync --frozen --group dev --all-extras - -# Execute the command passed to docker run exec "$@" diff --git a/docker/server-gpu.Dockerfile b/docker/server-gpu.Dockerfile index 4e6bc9a..bb192b2 100644 --- a/docker/server-gpu.Dockerfile +++ b/docker/server-gpu.Dockerfile @@ -1,35 +1,26 @@ # syntax=docker/dockerfile:1 -# GPU-enabled server Dockerfile with CUDA support -# Use this for systems with NVIDIA GPUs - +ARG PYTHON_VERSION=3.12 +ARG CUDA_VERSION=12.4.1 +ARG SPACY_MODEL_URL=https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl # ============================================================================= # Python Stage - Get Python 3.12 from official image # ============================================================================= -FROM python:3.12-slim-bookworm AS python-base +FROM python:${PYTHON_VERSION}-slim-bookworm AS python-base # ============================================================================= # Base Stage - NVIDIA CUDA with cuDNN for GPU-accelerated inference # ============================================================================= -# Using NVIDIA's official CUDA image with cuDNN 9.x for CTranslate2/faster-whisper -# The runtime image includes cuDNN libraries required for GPU inference -FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04 AS base +FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu22.04 AS base -# CUDA/cuDNN environment variables -# NOTE: PyTorch bundles cuDNN 9.8.0, but system has 9.1.0 -# We set LD_LIBRARY_PATH at runtime to prioritize PyTorch's bundled cuDNN ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ - # CUDA environment - these tell nvidia-container-runtime to inject GPU NVIDIA_VISIBLE_DEVICES=all \ NVIDIA_DRIVER_CAPABILITIES=compute,utility \ - # Base CUDA path (cuDNN paths added at runtime to use PyTorch's bundled version) LD_LIBRARY_PATH=/usr/local/cuda/lib64 \ - # Python path configuration PATH=/usr/local/bin:$PATH -# Copy Python 3.12 from official image (avoids PPA network issues) COPY --from=python-base /usr/local/bin/python3.12 /usr/local/bin/python3.12 COPY --from=python-base /usr/local/bin/python3 /usr/local/bin/python3 COPY --from=python-base /usr/local/bin/pip3 /usr/local/bin/pip3 @@ -37,94 +28,92 @@ COPY --from=python-base /usr/local/lib/python3.12 /usr/local/lib/python3.12 COPY --from=python-base /usr/local/include/python3.12 /usr/local/include/python3.12 COPY --from=python-base /usr/local/lib/libpython3.12.so* /usr/local/lib/ -# Create symlinks for python/pip commands RUN ln -sf /usr/local/bin/python3.12 /usr/local/bin/python \ && ln -sf /usr/local/bin/pip3 /usr/local/bin/pip \ && ldconfig -# Install uv and system dependencies COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -# Core build/runtime deps for project packages (sounddevice, asyncpg, cryptography). RUN apt-get update \ && apt-get install -y --no-install-recommends \ - build-essential \ - pkg-config \ - portaudio19-dev \ + libportaudio2 \ libsndfile1 \ && rm -rf /var/lib/apt/lists/* WORKDIR /workspace -# Copy dependency files first for better layer caching COPY pyproject.toml uv.lock* ./ # ============================================================================= -# Server Stage - GPU Enabled +# Production Server Stage (minimal deps) # ============================================================================= +FROM base AS build +ARG SPACY_MODEL_URL + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + portaudio19-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev + +COPY . . + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv pip install "${SPACY_MODEL_URL}" + FROM base AS server -# Install dependencies with CUDA-enabled PyTorch -# The --extra-index-url ensures we get CUDA-enabled torch +COPY --from=build /workspace /workspace + +COPY docker/entrypoint-gpu.sh /usr/local/bin/entrypoint-gpu.sh +RUN chmod +x /usr/local/bin/entrypoint-gpu.sh + +RUN useradd --create-home --shell /bin/bash noteflow +USER noteflow + +EXPOSE 50051 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import grpc; channel = grpc.insecure_channel('localhost:50051'); grpc.channel_ready_future(channel).result(timeout=5)" || exit 1 + +ENTRYPOINT ["/usr/local/bin/entrypoint-gpu.sh"] +CMD ["uv", "run", "python", "-m", "noteflow.grpc.server"] + +# ============================================================================= +# Development Server Stage (all extras + dev deps + hot reload) +# ============================================================================= +FROM base AS dev +ARG SPACY_MODEL_URL + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + portaudio19-dev \ + && rm -rf /var/lib/apt/lists/* + RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-install-project --group dev --all-extras -# Copy source code COPY . . -# Install the project itself RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --group dev --all-extras -# Install spaCy small English model for NER (baked into image) RUN --mount=type=cache,target=/root/.cache/uv \ - uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl + uv pip install "${SPACY_MODEL_URL}" -# Verify CUDA and cuDNN are accessible -# Note: torch.cuda.is_available() may return False during build (no GPU) -# but cuDNN libraries should be present in the image for runtime -RUN python -c "import torch; print(f'PyTorch: {torch.__version__}'); print(f'CUDA available: {torch.cuda.is_available()}'); print(f'cuDNN version: {torch.backends.cudnn.version() if torch.backends.cudnn.is_available() else \"N/A\"}')" || true -# Verify cuDNN shared libraries are present -RUN ldconfig -p | grep -i cudnn || echo "cuDNN libraries will be available at runtime" - -# Copy GPU entrypoint script that sets LD_LIBRARY_PATH correctly COPY docker/entrypoint-gpu.sh /usr/local/bin/entrypoint-gpu.sh RUN chmod +x /usr/local/bin/entrypoint-gpu.sh EXPOSE 50051 -# Use entrypoint script to set LD_LIBRARY_PATH correctly at runtime -# This ensures PyTorch's bundled cuDNN 9.8.0 takes priority over system cuDNN 9.1.0 -ENTRYPOINT ["/usr/local/bin/entrypoint-gpu.sh"] -CMD ["uv", "run", "python", "scripts/dev_watch_server.py"] - -# ============================================================================= -# Server Production Stage - GPU Enabled with all extras -# ============================================================================= -FROM base AS server-full - -# Install all dependencies including optional extras -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-install-project --group dev --all-extras - -# Copy source code -COPY . . - -# Install the project itself -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --group dev --all-extras - -# Install spaCy small English model for NER (baked into image) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl - -# Copy GPU entrypoint script that sets LD_LIBRARY_PATH correctly -COPY docker/entrypoint-gpu.sh /usr/local/bin/entrypoint-gpu.sh -RUN chmod +x /usr/local/bin/entrypoint-gpu.sh - -EXPOSE 50051 - -# Use entrypoint script to set LD_LIBRARY_PATH correctly at runtime -# This ensures PyTorch's bundled cuDNN 9.8.0 takes priority over system cuDNN 9.1.0 ENTRYPOINT ["/usr/local/bin/entrypoint-gpu.sh"] CMD ["uv", "run", "python", "scripts/dev_watch_server.py"] diff --git a/docker/server.Dockerfile b/docker/server.Dockerfile index e80456b..6858f1c 100644 --- a/docker/server.Dockerfile +++ b/docker/server.Dockerfile @@ -1,91 +1,88 @@ # syntax=docker/dockerfile:1 -FROM python:3.12-bookworm AS base +ARG PYTHON_VERSION=3.12 +ARG SPACY_MODEL_URL=https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl + +FROM python:${PYTHON_VERSION}-slim-bookworm AS base ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy -# Install uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -# Core build/runtime deps for project packages (sounddevice, asyncpg, cryptography). RUN apt-get update \ && apt-get install -y --no-install-recommends \ - build-essential \ - pkg-config \ - portaudio19-dev \ + libportaudio2 \ libsndfile1 \ && rm -rf /var/lib/apt/lists/* WORKDIR /workspace -# Copy dependency files first for better layer caching COPY pyproject.toml uv.lock* ./ # ============================================================================= -# Server Stage +# Production Server Stage (minimal deps, no dev tooling) # ============================================================================= +FROM base AS build +ARG SPACY_MODEL_URL + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + portaudio19-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev + +COPY . . + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv pip install "${SPACY_MODEL_URL}" + FROM base AS server -# Install dependencies (server needs dev deps for watchfiles) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-install-project --group dev --all-extras +COPY --from=build /workspace /workspace -# Copy source code -COPY . . - -# Install the project itself -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --group dev --all-extras - -# Install spaCy small English model for NER (baked into image) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl +RUN useradd --create-home --shell /bin/bash noteflow +USER noteflow EXPOSE 50051 -CMD ["sh", "-c", "uv sync --frozen --group dev --all-extras && uv run python scripts/dev_watch_server.py"] +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import grpc; channel = grpc.insecure_channel('localhost:50051'); grpc.channel_ready_future(channel).result(timeout=5)" || exit 1 + +CMD ["uv", "run", "python", "-m", "noteflow.grpc.server"] # ============================================================================= -# Server Production Stage (all optional dependencies) +# Development Server Stage (all extras + dev deps + hot reload) # ============================================================================= -FROM base AS server-full - -# Install all dependencies including optional extras -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-install-project --group dev --all-extras - -# Copy source code -COPY . . - -# Install the project itself -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --group dev --all-extras - -# Install spaCy small English model for NER (baked into image) -RUN --mount=type=cache,target=/root/.cache/uv \ - uv pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl - -EXPOSE 50051 - -CMD ["sh", "-c", "uv sync --frozen --group dev --all-extras && uv run python scripts/dev_watch_server.py"] - -# ----------------------------------------------------------------------------- -# NER stage: Add spaCy model for named entity recognition -# ----------------------------------------------------------------------------- -FROM base AS with-ner - -# Install NER dependencies and download spaCy model -RUN uv pip install -e ".[ner]" \ - && uv run python -m spacy download en_core_web_sm - -# Verify model is available -RUN uv run python -c "import spacy; nlp = spacy.load('en_core_web_sm'); print('NER model loaded successfully')" - -# ----------------------------------------------------------------------------- -# Development target (default) -# ----------------------------------------------------------------------------- FROM base AS dev +ARG SPACY_MODEL_URL -CMD ["sh", "-c", "uv sync --frozen --group dev --all-extras && uv run python scripts/dev_watch_server.py"] +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + portaudio19-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --group dev --all-extras + +COPY . . + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --group dev --all-extras + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv pip install "${SPACY_MODEL_URL}" + +EXPOSE 50051 + +CMD ["uv", "run", "python", "scripts/dev_watch_server.py"] diff --git a/pyproject.toml b/pyproject.toml index e161300..93fdd17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ dependencies = [ "cryptography>=42.0", # gRPC Client-Server "grpcio>=1.60", - "grpcio-tools>=1.60", "protobuf>=4.25", # Database (async PostgreSQL + pgvector) "sqlalchemy[asyncio]>=2.0", @@ -28,7 +27,6 @@ dependencies = [ "httpx>=0.27", "authlib>=1.6.6", "rich>=14.2.0", - "types-psutil>=7.2.0.20251228", # Structured logging "structlog>=24.0", "sounddevice>=0.5.3", @@ -36,7 +34,6 @@ dependencies = [ "openai-whisper>=20250625", "langgraph>=1.0.6", "langgraph-checkpoint-postgres>=3.0.3", - "langgraph-checkpoint-redis>=0.3.2", "psycopg>=3.3.2", ] @@ -52,9 +49,11 @@ dev = [ "mypy>=1.8", "ruff>=0.3", "basedpyright>=1.18", + "grpcio-tools>=1.60", "pyrefly>=0.46.1", "sourcery; sys_platform == 'darwin'", "types-grpcio==1.0.0.20251001", + "types-psutil>=7.2.0.20251228", "testcontainers[postgres]>=4.0", ] triggers = [