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.

This commit is contained in:
2026-01-24 14:50:19 +00:00
parent 09d70af58f
commit acfba090e4
29 changed files with 2676 additions and 918 deletions

118
.gitea/workflows/build.yml Normal file
View File

@@ -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 }}

103
.gitea/workflows/ci.yml Normal file
View File

@@ -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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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
)
```

View File

@@ -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
```

View File

@@ -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
)
```

View File

@@ -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

View File

@@ -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

View File

@@ -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<ConnectionResult>;
connect(serverUrl?: string): Promise<ServerInfo>;
disconnect(): Promise<void>;
isConnected(): boolean;
getEffectiveServerUrl(): Promise<string>;
isConnected(): Promise<boolean>;
getEffectiveServerUrl(): Promise<EffectiveServerUrl>;
getServerInfo(): Promise<ServerInfo>;
// Identity (Sprint 16)
getCurrentUser(): Promise<GetCurrentUserResponse>;
listWorkspaces(): Promise<ListWorkspacesResponse>;
switchWorkspace(workspaceId: string): Promise<SwitchWorkspaceResponse>;
getWorkspaceSettings(request): Promise<GetWorkspaceSettingsResponse>;
updateWorkspaceSettings(request): Promise<GetWorkspaceSettingsResponse>;
// Authentication
initiateAuthLogin(provider, redirectUri?): Promise<InitiateAuthLoginResponse>;
completeAuthLogin(provider, code, state): Promise<CompleteAuthLoginResponse>;
logout(provider?): Promise<LogoutResponse>;
// Projects (Sprint 18)
createProject(request): Promise<Project>;
getProject(request): Promise<Project>;
listProjects(request): Promise<ListProjectsResponse>;
updateProject(request): Promise<Project>;
archiveProject(projectId): Promise<Project>;
setActiveProject(request): Promise<void>;
addProjectMember(request): Promise<ProjectMembership>;
// ... more project methods
// Meetings
createMeeting(request: CreateMeetingRequest): Promise<Meeting>;
getMeeting(request: GetMeetingRequest): Promise<Meeting>;
listMeetings(request: ListMeetingsRequest): Promise<ListMeetingsResponse>;
stopMeeting(request: StopMeetingRequest): Promise<Meeting>;
deleteMeeting(request: DeleteMeetingRequest): Promise<void>;
createMeeting(request): Promise<Meeting>;
getMeeting(request): Promise<Meeting>;
listMeetings(request): Promise<ListMeetingsResponse>;
stopMeeting(meetingId): Promise<Meeting>;
deleteMeeting(meetingId): Promise<boolean>;
// Streaming
startTranscription(meetingId: string): TranscriptionStream;
startTranscription(meetingId): Promise<TranscriptionStream>;
getStreamState(): Promise<StreamStateInfo>;
resetStreamState(): Promise<StreamStateInfo>;
// Diarization
refineSpeakerDiarization(request: RefineDiarizationRequest): Promise<DiarizationJob>;
getDiarizationJobStatus(request: GetJobStatusRequest): Promise<DiarizationJobStatus>;
renameSpeaker(request: RenameSpeakerRequest): Promise<void>;
// Summary & AI
generateSummary(meetingId, forceRegenerate?): Promise<Summary>;
grantCloudConsent(): Promise<void>;
revokeCloudConsent(): Promise<void>;
getCloudConsentStatus(): Promise<CloudConsentStatus>;
askAssistant(request): Promise<AskAssistantResponse>;
streamAssistant(request): Promise<void>;
// Summaries
generateSummary(request: GenerateSummaryRequest): Promise<Summary>;
listSummarizationTemplates(): Promise<SummarizationTemplate[]>;
createSummarizationTemplate(request: CreateTemplateRequest): Promise<SummarizationTemplate>;
// ASR & Streaming Config
getAsrConfiguration(): Promise<ASRConfiguration>;
updateAsrConfiguration(request): Promise<UpdateASRConfigurationResult>;
getStreamingConfiguration(): Promise<StreamingConfiguration>;
updateStreamingConfiguration(request): Promise<StreamingConfiguration>;
// HuggingFace Token
setHuggingFaceToken(request): Promise<SetHuggingFaceTokenResult>;
getHuggingFaceTokenStatus(): Promise<HuggingFaceTokenStatus>;
deleteHuggingFaceToken(): Promise<boolean>;
validateHuggingFaceToken(): Promise<ValidateHuggingFaceTokenResult>;
// Annotations
addAnnotation(request: AddAnnotationRequest): Promise<Annotation>;
listAnnotations(request: ListAnnotationsRequest): Promise<Annotation[]>;
updateAnnotation(request: UpdateAnnotationRequest): Promise<Annotation>;
deleteAnnotation(request: DeleteAnnotationRequest): Promise<void>;
listAnnotations(meetingId, startTime?, endTime?): Promise<Annotation[]>;
addAnnotation(request): Promise<Annotation>;
updateAnnotation(request): Promise<Annotation>;
deleteAnnotation(annotationId): Promise<boolean>;
// Export
exportTranscript(request: ExportRequest): Promise<ExportResult>;
exportTranscript(meetingId, format): Promise<ExportResult>;
saveExportFile(content, defaultName, extension): Promise<boolean>;
// ... 50+ more methods
// Playback (desktop)
startPlayback(meetingId, startTime?): Promise<void>;
pausePlayback(): Promise<void>;
stopPlayback(): Promise<void>;
seekPlayback(position): Promise<PlaybackInfo>;
getPlaybackState(): Promise<PlaybackInfo>;
// Diarization
refineSpeakers(meetingId, numSpeakers?): Promise<DiarizationJobStatus>;
getDiarizationJobStatus(jobId): Promise<DiarizationJobStatus>;
renameSpeaker(meetingId, oldSpeakerId, newName): Promise<boolean>;
cancelDiarization(jobId): Promise<CancelDiarizationResult>;
getActiveDiarizationJobs(): Promise<DiarizationJobStatus[]>;
// Audio Devices (desktop)
listAudioDevices(): Promise<AudioDeviceInfo[]>;
getDefaultAudioDevice(isInput): Promise<AudioDeviceInfo | null>;
selectAudioDevice(deviceId, isInput): Promise<void>;
listLoopbackDevices(): Promise<AudioDeviceInfo[]>;
setSystemAudioDevice(deviceId): Promise<void>;
setDualCaptureEnabled(enabled): Promise<void>;
getDualCaptureConfig(): Promise<DualCaptureConfigInfo>;
// Triggers (desktop)
setTriggerEnabled(enabled): Promise<void>;
snoozeTriggers(minutes?): Promise<void>;
getTriggerStatus(): Promise<TriggerStatus>;
dismissTrigger(): Promise<void>;
acceptTrigger(title?): Promise<Meeting>;
// Entities (NER)
extractEntities(meetingId, forceRefresh?): Promise<ExtractEntitiesResponse>;
updateEntity(meetingId, entityId, text?, category?): Promise<ExtractedEntity>;
deleteEntity(meetingId, entityId): Promise<boolean>;
// Calendar
listCalendarEvents(hoursAhead?, limit?, provider?): Promise<ListCalendarEventsResponse>;
getCalendarProviders(): Promise<GetCalendarProvidersResponse>;
initiateCalendarAuth(provider, redirectUri?): Promise<InitiateCalendarAuthResponse>;
completeCalendarAuth(provider, code, state): Promise<CompleteCalendarAuthResponse>;
getOAuthConnectionStatus(provider): Promise<GetOAuthConnectionStatusResponse>;
disconnectCalendar(provider): Promise<DisconnectOAuthResponse>;
// Webhooks
registerWebhook(request): Promise<RegisteredWebhook>;
listWebhooks(enabledOnly?): Promise<ListWebhooksResponse>;
updateWebhook(request): Promise<RegisteredWebhook>;
deleteWebhook(webhookId): Promise<DeleteWebhookResponse>;
getWebhookDeliveries(webhookId, limit?): Promise<GetWebhookDeliveriesResponse>;
// Integration Sync
startIntegrationSync(integrationId): Promise<StartIntegrationSyncResponse>;
getSyncStatus(syncRunId): Promise<GetSyncStatusResponse>;
listSyncHistory(integrationId, limit?, offset?): Promise<ListSyncHistoryResponse>;
getUserIntegrations(): Promise<GetUserIntegrationsResponse>;
// Observability
getRecentLogs(request?): Promise<GetRecentLogsResponse>;
getPerformanceMetrics(request?): Promise<GetPerformanceMetricsResponse>;
runConnectionDiagnostics(): Promise<ConnectionDiagnostics>;
// OIDC Provider Management (Sprint 17)
registerOidcProvider(request): Promise<OidcProviderApi>;
listOidcProviders(workspaceId?, enabledOnly?): Promise<ListOidcProvidersResponse>;
getOidcProvider(providerId): Promise<OidcProviderApi>;
updateOidcProvider(request): Promise<OidcProviderApi>;
deleteOidcProvider(providerId): Promise<DeleteOidcProviderResponse>;
listOidcPresets(): Promise<ListOidcPresetsResponse>;
// Tasks (Strategy B)
listTasks(request): Promise<ListTasksResponse>;
createTask(request): Promise<Task>;
updateTask(request): Promise<Task>;
// Analytics (Strategy B)
getAnalyticsOverview(request): Promise<AnalyticsOverview>;
listSpeakerStats(request): Promise<ListSpeakerStatsResponse>;
getEntityAnalytics(request): Promise<EntityAnalytics>;
// Summarization Templates
listSummarizationTemplates(request): Promise<ListSummarizationTemplatesResponse>;
createSummarizationTemplate(request): Promise<SummarizationTemplateMutationResponse>;
updateSummarizationTemplate(request): Promise<SummarizationTemplateMutationResponse>;
archiveSummarizationTemplate(request): Promise<SummarizationTemplate>;
// Installed Apps (desktop)
listInstalledApps(options?): Promise<ListInstalledAppsResponse>;
invalidateAppCache(): Promise<void>;
// Testing (E2E)
checkTestEnvironment(): Promise<TestEnvironmentInfo>;
injectTestAudio(meetingId, config): Promise<TestAudioResult>;
injectTestTone(meetingId, frequencyHz, durationSeconds, sampleRate?): Promise<TestAudioResult>;
}
```
## Transcription Streaming
## Adapter Structure
```typescript
interface TranscriptionStream {
send(chunk: AudioChunk): void;
onUpdate(callback: (update: TranscriptUpdate) => void): Promise<void> | 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<Meeting> {
return meetingCache.get(id) ?? rejectReadOnly();
},
async createMeeting(): Promise<Meeting> {
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<NoteFlowAPI> {
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') {

View File

@@ -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<void>;
revokeConsent: () => Promise<void>;
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<void>;
revokeConsent: () => Promise<void>;
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<T>()` — 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 <OfflineBanner />;
@@ -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';
```

View File

@@ -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 <OfflineBanner />;
@@ -114,16 +135,31 @@ export default function Recording() {
| `<WorkspaceProvider>` | Wraps app with workspace state |
| `<ProjectProvider>` | Wraps app with project state |
| `<AppLayout>` | Main app shell with sidebar |
| `<AudioLevelMeter>` | Real-time audio VU meter |
| `<RecordingHeader>` | Recording session metadata |
| `<EntityHighlight>` | Inline NER entity highlighting |
| `<AnnotationTypeBadge>` | Action item/decision/risk badge |
| `<MeetingStateBadge>` | Meeting state indicator |
| `<OfflineBanner>` | Cached/offline mode warning |
| `<ProcessingStatus>` | Post-processing progress |
| `<ApiModeIndicator>` | Connection mode display |
| `<ConfirmationDialog>` | Generic confirmation modal |
| `<EmptyState>` | Empty state placeholder |
| `<StatsCard>` | 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
```

View File

@@ -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
```

View File

@@ -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 |

View File

@@ -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/`)

View File

@@ -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

233
.rag/14-testing.md Normal file
View File

@@ -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(<MyComponent {...props} />);
// 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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"]
}

207
docker-compose.dev.yml Normal file
View File

@@ -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/tcp/localhost/6333'"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
networks:
- noteflow-net
server-dev:
container_name: noteflow-server-dev
build:
context: .
dockerfile: docker/server.Dockerfile
target: dev
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: cpu
volumes:
- .:/workspace
- server_venv:/workspace/.venv
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
server-gpu-dev:
container_name: noteflow-server-dev
build:
context: .
dockerfile: docker/server-gpu.Dockerfile
target: dev
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: cuda
NOTEFLOW_DIARIZATION_DEVICE: cuda
volumes:
- .:/workspace
- server_venv_gpu:/workspace/.venv
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
profiles:
- gpu
server-rocm-dev:
container_name: noteflow-server-dev
build:
context: .
dockerfile: docker/Dockerfile.rocm
target: dev
args:
ROCM_VERSION: ${ROCM_VERSION:-6.4.1}
ROCM_PYTORCH_RELEASE: ${ROCM_PYTORCH_RELEASE:-2.6.0}
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: rocm
NOTEFLOW_DIARIZATION_DEVICE: auto
NOTEFLOW_FEATURE_ROCM_ENABLED: "true"
volumes:
- .:/workspace
devices:
- /dev/kfd
- /dev/dri
group_add:
- ${VIDEO_GID:-44}
- ${RENDER_GID:-993}
security_opt:
- seccomp=unconfined
tty: true
ulimits:
memlock: -1
stack: 67108864
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
profiles:
- rocm
frontend:
container_name: noteflow-frontend
image: node:20-alpine
working_dir: /app
ports:
- "5173:5173"
volumes:
- ./client:/app
command: sh -c "npm install && npm run dev"
environment:
- NODE_ENV=development
- VITE_HMR_HOST=${VITE_HMR_HOST:-localhost}
networks:
- noteflow-net
volumes:
noteflow_pg_data:
noteflow_redis_data:
noteflow_qdrant_data:
server_venv:
server_venv_gpu:
networks:
noteflow-net:
driver: bridge

169
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,169 @@
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/tcp/localhost/6333'"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
networks:
- noteflow-net
server:
container_name: noteflow-server
image: git.baked.rocks/vasceannie/noteflow:latest
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: cpu
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
server-gpu:
container_name: noteflow-server
image: git.baked.rocks/vasceannie/noteflow:latest-gpu
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: cuda
NOTEFLOW_DIARIZATION_DEVICE: cuda
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
profiles:
- gpu
server-rocm:
container_name: noteflow-server
image: git.baked.rocks/vasceannie/noteflow:latest-rocm
restart: unless-stopped
ports:
- "50051:50051"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
environment:
NOTEFLOW_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-noteflow}:${POSTGRES_PASSWORD:-noteflow}@db:5432/${POSTGRES_DB:-noteflow}
NOTEFLOW_REDIS_URL: redis://redis:6379/0
NOTEFLOW_QDRANT_URL: http://qdrant:6333
NOTEFLOW_LOG_FORMAT: console
NOTEFLOW_ASR_DEVICE: rocm
NOTEFLOW_DIARIZATION_DEVICE: auto
NOTEFLOW_FEATURE_ROCM_ENABLED: "true"
devices:
- /dev/kfd
- /dev/dri
group_add:
- ${VIDEO_GID:-44}
- ${RENDER_GID:-993}
security_opt:
- seccomp=unconfined
ulimits:
memlock: -1
stack: 67108864
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_healthy
networks:
- noteflow-net
profiles:
- rocm
volumes:
noteflow_pg_data:
noteflow_redis_data:
noteflow_qdrant_data:
networks:
noteflow-net:
driver: bridge

View File

@@ -1,16 +1,4 @@
# syntax=docker/dockerfile:1
# NoteFlow ROCm Docker Image
# For AMD GPU support using PyTorch ROCm
#
# Build:
# docker build -f docker/Dockerfile.rocm -t noteflow:rocm .
#
# Run (with GPU access):
# docker run --device=/dev/kfd --device=/dev/dri --group-add video --group-add render \
# --security-opt seccomp=unconfined \
# -v /path/to/models:/workspace/models \
# noteflow:rocm
ARG ROCM_VERSION=6.4.1
ARG ROCM_PYTORCH_RELEASE=2.6.0
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
@@ -29,10 +17,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
UV_LINK_MODE=copy \
PATH=/usr/local/bin:$PATH
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
@@ -43,72 +29,51 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
# =============================================================================
# Server Stage - ROCm
# =============================================================================
FROM base AS server
WORKDIR /workspace
# Copy dependency files first for better layer caching
COPY pyproject.toml uv.lock* ./
COPY README.md ./
COPY src ./src/
# Create venv with access to system site-packages (ROCm torch)
ENV VIRTUAL_ENV=/opt/venv
RUN uv venv --system-site-packages ${VIRTUAL_ENV}
ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
# Install NoteFlow with ROCm extras (into venv)
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python -e ".[rocm,optional]"
# Improve redis client performance and silence hiredis warning.
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python hiredis
# Install spaCy small English model for NER (baked into image)
# =============================================================================
# Production Server Stage (minimal deps)
# =============================================================================
FROM base AS server
ARG SPACY_MODEL_URL
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python -e ".[rocm]"
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python ${SPACY_MODEL_URL}
# Copy remaining files (scripts, configs, etc.)
COPY . .
# Environment variables for ROCm
ENV ROCM_PATH=/opt/rocm \
HIP_VISIBLE_DEVICES=0 \
HSA_OVERRIDE_GFX_VERSION="" \
NOTEFLOW_ASR_DEVICE=rocm \
NOTEFLOW_FEATURE_ROCM_ENABLED=true
# gRPC server port
RUN useradd --create-home --shell /bin/bash noteflow
USER noteflow
EXPOSE 50051
# Health check
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
# Run gRPC server
CMD ["python", "-m", "noteflow.grpc.server"]
# =============================================================================
# Server Dev Stage - ROCm (hot reload)
# Development Server Stage (all extras + hot reload)
# =============================================================================
FROM base AS server-dev
WORKDIR /workspace
COPY pyproject.toml uv.lock* ./
COPY README.md ./
COPY src ./src/
ENV VIRTUAL_ENV=/opt/venv
RUN uv venv --system-site-packages ${VIRTUAL_ENV}
ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
FROM base AS dev
ARG SPACY_MODEL_URL
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python -e ".[rocm,optional]"
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python hiredis
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python watchfiles
ARG SPACY_MODEL_URL
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python ${SPACY_MODEL_URL}
COPY . .
@@ -122,38 +87,3 @@ ENV ROCM_PATH=/opt/rocm \
EXPOSE 50051
CMD ["python", "scripts/dev_watch_server.py"]
# =============================================================================
# Server Full Stage - ROCm (optional extras)
# =============================================================================
FROM base AS server-full
WORKDIR /workspace
COPY pyproject.toml uv.lock* ./
COPY README.md ./
COPY src ./src/
ENV VIRTUAL_ENV=/opt/venv
RUN uv venv --system-site-packages ${VIRTUAL_ENV}
ENV PATH="${VIRTUAL_ENV}/bin:$PATH"
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python -e ".[rocm,optional]"
ARG SPACY_MODEL_URL
RUN uv pip install --python ${VIRTUAL_ENV}/bin/python ${SPACY_MODEL_URL}
COPY . .
ENV ROCM_PATH=/opt/rocm \
HIP_VISIBLE_DEVICES=0 \
HSA_OVERRIDE_GFX_VERSION="" \
NOTEFLOW_ASR_DEVICE=rocm \
NOTEFLOW_FEATURE_ROCM_ENABLED=true
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
CMD ["python", "-m", "noteflow.grpc.server"]

View File

@@ -1,21 +1,8 @@
#!/bin/bash
# GPU entrypoint script
# Sets LD_LIBRARY_PATH to prioritize PyTorch's bundled cuDNN 9.8.0 over system cuDNN 9.1.0
set -e
# PyTorch bundles cuDNN 9.8.0 in its site-packages
# We must add these paths FIRST to override system cuDNN 9.1.0
PYTORCH_NVIDIA_LIBS="/workspace/.venv/lib/python3.12/site-packages/nvidia"
export LD_LIBRARY_PATH="${PYTORCH_NVIDIA_LIBS}/cudnn/lib:${PYTORCH_NVIDIA_LIBS}/cublas/lib:${PYTORCH_NVIDIA_LIBS}/cuda_runtime/lib:${PYTORCH_NVIDIA_LIBS}/cufft/lib:${PYTORCH_NVIDIA_LIBS}/cusolver/lib:${PYTORCH_NVIDIA_LIBS}/cusparse/lib:${PYTORCH_NVIDIA_LIBS}/nccl/lib:${PYTORCH_NVIDIA_LIBS}/nvtx/lib:/usr/local/cuda/lib64"
echo "=== GPU Entrypoint ==="
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "Checking cuDNN libraries..."
ls -la "${PYTORCH_NVIDIA_LIBS}/cudnn/lib/" 2>/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 "$@"

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -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 = [