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:
118
.gitea/workflows/build.yml
Normal file
118
.gitea/workflows/build.yml
Normal 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
103
.gitea/workflows/ci.yml
Normal 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
|
||||
103
.gitea/workflows/proto-sync.yml
Normal file
103
.gitea/workflows/proto-sync.yml
Normal 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 }}
|
||||
141
.gitea/workflows/quality.yml
Normal file
141
.gitea/workflows/quality.yml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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';
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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/`)
|
||||
|
||||
|
||||
@@ -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
233
.rag/14-testing.md
Normal 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
|
||||
55
README.md
55
README.md
@@ -50,34 +50,47 @@ The Tauri desktop app requires a working Rust toolchain.
|
||||
|
||||
## Container-based development
|
||||
|
||||
The repository includes a `compose.yaml` with a server container (and a commented-out Postgres service).
|
||||
The repository includes two compose files:
|
||||
- `docker-compose.prod.yml` — Production deployment with prebuilt images
|
||||
- `docker-compose.dev.yml` — Development with hot reload and all extras
|
||||
|
||||
### Option A: Run the server in Docker, clients locally
|
||||
|
||||
1) Create a `.env` file from `example.env` and set any needed settings.
|
||||
2) Start the server container:
|
||||
### Production (prebuilt images)
|
||||
|
||||
```bash
|
||||
docker compose up -d server
|
||||
# CPU server (default)
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# NVIDIA GPU server
|
||||
docker compose -f docker-compose.prod.yml --profile gpu up -d
|
||||
|
||||
# AMD ROCm GPU server
|
||||
docker compose -f docker-compose.prod.yml --profile rocm up -d
|
||||
```
|
||||
|
||||
The server will expose `50051` on the host; point your client at `localhost:50051`.
|
||||
|
||||
### Option B: Enable PostgreSQL in Docker
|
||||
|
||||
`compose.yaml` includes a commented `db` service using `pgvector/pgvector:pg15`. To use it:
|
||||
|
||||
1) Uncomment the `db` service and `depends_on`/`environment` lines in `compose.yaml`.
|
||||
2) Set `NOTEFLOW_DATABASE_URL` to the container URL (example):
|
||||
|
||||
```
|
||||
postgresql+asyncpg://noteflow:noteflow@db:5432/noteflow
|
||||
```
|
||||
|
||||
3) Start services:
|
||||
### Development (with hot reload)
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
# CPU dev server + frontend
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
|
||||
# NVIDIA GPU dev server
|
||||
docker compose -f docker-compose.dev.yml --profile gpu up -d
|
||||
|
||||
# AMD ROCm dev server
|
||||
docker compose -f docker-compose.dev.yml --profile rocm up -d
|
||||
```
|
||||
|
||||
### Building images
|
||||
|
||||
```bash
|
||||
# Build all production images
|
||||
docker buildx bake prod
|
||||
|
||||
# Build and push to registry
|
||||
REGISTRY=git.baked.rocks/vasceannie docker buildx bake --push prod
|
||||
|
||||
# Build dev images locally
|
||||
docker buildx bake dev
|
||||
```
|
||||
|
||||
## Common commands
|
||||
|
||||
@@ -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"
|
||||
294
docker-bake.hcl
294
docker-bake.hcl
@@ -2,30 +2,27 @@
|
||||
# Docker Buildx Bake configuration for NoteFlow
|
||||
#
|
||||
# Usage:
|
||||
# docker buildx bake # Build default targets
|
||||
# docker buildx bake # Build default targets (CPU server)
|
||||
# docker buildx bake prod # Build all production images
|
||||
# docker buildx bake dev # Build all dev images
|
||||
# docker buildx bake server # Build CPU server only
|
||||
# docker buildx bake server-gpu # Build GPU server only
|
||||
# docker buildx bake server-gpu # Build CUDA GPU server only
|
||||
# docker buildx bake server-rocm # Build ROCm GPU server only
|
||||
# docker buildx bake server-rocm-dev # Build ROCm GPU dev server (hot reload)
|
||||
# docker buildx bake servers # Build all server variants (parallel)
|
||||
# docker buildx bake servers-gpu # Build GPU variants only (CUDA + ROCm)
|
||||
# docker buildx bake client # Build client targets
|
||||
# docker buildx bake all # Build everything
|
||||
# docker buildx bake --print # Show build plan without building
|
||||
# docker buildx bake --push prod # Build and push production images
|
||||
#
|
||||
# With specific options:
|
||||
# docker buildx bake server --set server.tags=myregistry/noteflow:v1.0
|
||||
# docker buildx bake --push all # Build and push all images
|
||||
# Registry configuration:
|
||||
# REGISTRY=git.baked.rocks/vasceannie docker buildx bake --push prod
|
||||
|
||||
# =============================================================================
|
||||
# Variables
|
||||
# =============================================================================
|
||||
|
||||
variable "REGISTRY" {
|
||||
default = ""
|
||||
default = "git.baked.rocks/vasceannie"
|
||||
}
|
||||
|
||||
variable "IMAGE_PREFIX" {
|
||||
variable "IMAGE_NAME" {
|
||||
default = "noteflow"
|
||||
}
|
||||
|
||||
@@ -54,60 +51,85 @@ variable "SPACY_MODEL_URL" {
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Functions
|
||||
# Functions - Single repo tagging scheme
|
||||
# =============================================================================
|
||||
|
||||
function "tag" {
|
||||
params = [name]
|
||||
result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-${name}:${TAG}" : "${IMAGE_PREFIX}-${name}:${TAG}"
|
||||
# Base image path: REGISTRY/IMAGE_NAME
|
||||
function "image_base" {
|
||||
params = []
|
||||
result = REGISTRY != "" ? "${REGISTRY}/${IMAGE_NAME}" : IMAGE_NAME
|
||||
}
|
||||
|
||||
function "tags" {
|
||||
params = [name]
|
||||
result = [
|
||||
tag(name),
|
||||
REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-${name}:latest" : "${IMAGE_PREFIX}-${name}:latest"
|
||||
# CPU server tags: :TAG and :latest
|
||||
function "tags_cpu" {
|
||||
params = []
|
||||
result = TAG == "latest" ? [
|
||||
"${image_base()}:latest"
|
||||
] : [
|
||||
"${image_base()}:${TAG}",
|
||||
"${image_base()}:latest"
|
||||
]
|
||||
}
|
||||
|
||||
# GPU (CUDA) server tags: :TAG-gpu and :latest-gpu
|
||||
function "tags_gpu" {
|
||||
params = []
|
||||
result = TAG == "latest" ? [
|
||||
"${image_base()}:latest-gpu"
|
||||
] : [
|
||||
"${image_base()}:${TAG}-gpu",
|
||||
"${image_base()}:latest-gpu"
|
||||
]
|
||||
}
|
||||
|
||||
# ROCm server tags: :TAG-rocm and :latest-rocm
|
||||
function "tags_rocm" {
|
||||
params = []
|
||||
result = TAG == "latest" ? [
|
||||
"${image_base()}:latest-rocm"
|
||||
] : [
|
||||
"${image_base()}:${TAG}-rocm",
|
||||
"${image_base()}:latest-rocm"
|
||||
]
|
||||
}
|
||||
|
||||
# Dev tags (not published, local only)
|
||||
function "tags_dev" {
|
||||
params = [suffix]
|
||||
result = ["${image_base()}:dev${suffix}"]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Groups - Enable parallel builds
|
||||
# Groups - Organized by use case
|
||||
# =============================================================================
|
||||
|
||||
group "default" {
|
||||
targets = ["server"]
|
||||
}
|
||||
|
||||
# Production images (publishable)
|
||||
group "prod" {
|
||||
targets = ["server", "server-gpu", "server-rocm"]
|
||||
}
|
||||
|
||||
# Development images (local only)
|
||||
group "dev" {
|
||||
targets = ["server-dev", "server-gpu-dev", "server-rocm-dev"]
|
||||
}
|
||||
|
||||
# All server variants
|
||||
group "servers" {
|
||||
targets = ["server", "server-gpu", "server-rocm"]
|
||||
}
|
||||
|
||||
# GPU variants only
|
||||
group "servers-gpu" {
|
||||
targets = ["server-gpu", "server-rocm"]
|
||||
}
|
||||
|
||||
group "servers-full" {
|
||||
targets = ["server", "server-full", "server-gpu", "server-gpu-full", "server-rocm", "server-rocm-full"]
|
||||
}
|
||||
|
||||
group "client" {
|
||||
targets = ["client-build", "client-dev"]
|
||||
}
|
||||
|
||||
group "all" {
|
||||
targets = [
|
||||
"server",
|
||||
"server-full",
|
||||
"server-gpu",
|
||||
"server-gpu-full",
|
||||
"server-rocm",
|
||||
"server-rocm-full",
|
||||
"client-build"
|
||||
]
|
||||
}
|
||||
|
||||
# CI targets with GHA caching
|
||||
group "ci" {
|
||||
targets = ["server", "server-gpu", "server-rocm", "client-build"]
|
||||
targets = ["server-ci", "server-gpu-ci", "server-rocm-ci"]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
@@ -130,7 +152,7 @@ target "_server-common" {
|
||||
SPACY_MODEL_URL = SPACY_MODEL_URL
|
||||
}
|
||||
cache-from = [
|
||||
"type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server:cache" : "${IMAGE_PREFIX}-server:cache"}"
|
||||
"type=registry,ref=${image_base()}:cache"
|
||||
]
|
||||
cache-to = [
|
||||
"type=inline"
|
||||
@@ -146,7 +168,7 @@ target "_server-gpu-common" {
|
||||
SPACY_MODEL_URL = SPACY_MODEL_URL
|
||||
}
|
||||
cache-from = [
|
||||
"type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server-gpu:cache" : "${IMAGE_PREFIX}-server-gpu:cache"}"
|
||||
"type=registry,ref=${image_base()}:cache-gpu"
|
||||
]
|
||||
cache-to = [
|
||||
"type=inline"
|
||||
@@ -162,18 +184,7 @@ target "_server-rocm-common" {
|
||||
SPACY_MODEL_URL = SPACY_MODEL_URL
|
||||
}
|
||||
cache-from = [
|
||||
"type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-server-rocm:cache" : "${IMAGE_PREFIX}-server-rocm:cache"}"
|
||||
]
|
||||
cache-to = [
|
||||
"type=inline"
|
||||
]
|
||||
}
|
||||
|
||||
target "_client-common" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "docker/client.Dockerfile"
|
||||
cache-from = [
|
||||
"type=registry,ref=${REGISTRY != "" ? "${REGISTRY}/${IMAGE_PREFIX}-client:cache" : "${IMAGE_PREFIX}-client:cache"}"
|
||||
"type=registry,ref=${image_base()}:cache-rocm"
|
||||
]
|
||||
cache-to = [
|
||||
"type=inline"
|
||||
@@ -181,58 +192,27 @@ target "_client-common" {
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Server Targets (CPU)
|
||||
# Production Server Targets
|
||||
# =============================================================================
|
||||
|
||||
# CPU server (multi-arch: amd64 + arm64)
|
||||
target "server" {
|
||||
inherits = ["_server-common"]
|
||||
target = "server"
|
||||
tags = tags("server")
|
||||
tags = tags_cpu()
|
||||
platforms = ["linux/amd64", "linux/arm64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server (CPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server - CPU-only build"
|
||||
"org.opencontainers.image.title" = "NoteFlow Server"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server - CPU multi-arch build"
|
||||
}
|
||||
}
|
||||
|
||||
target "server-full" {
|
||||
inherits = ["_server-common"]
|
||||
target = "server-full"
|
||||
tags = tags("server-full")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Full (CPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - CPU-only build"
|
||||
}
|
||||
}
|
||||
|
||||
target "server-dev" {
|
||||
inherits = ["_server-common"]
|
||||
target = "dev"
|
||||
tags = tags("server-dev")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Dev (CPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow development server - CPU-only build"
|
||||
}
|
||||
}
|
||||
|
||||
target "server-ner" {
|
||||
inherits = ["_server-common"]
|
||||
target = "with-ner"
|
||||
tags = tags("server-ner")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server with NER (CPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow server with spaCy NER - CPU-only build"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Server Targets (GPU - NVIDIA CUDA)
|
||||
# =============================================================================
|
||||
|
||||
# CUDA GPU server (amd64 only)
|
||||
target "server-gpu" {
|
||||
inherits = ["_server-gpu-common"]
|
||||
target = "server"
|
||||
tags = tags("server-gpu")
|
||||
platforms = ["linux/amd64"] # GPU images are x86_64 only
|
||||
tags = tags_gpu()
|
||||
platforms = ["linux/amd64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server (GPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server - NVIDIA CUDA GPU build"
|
||||
@@ -240,26 +220,11 @@ target "server-gpu" {
|
||||
}
|
||||
}
|
||||
|
||||
target "server-gpu-full" {
|
||||
inherits = ["_server-gpu-common"]
|
||||
target = "server-full"
|
||||
tags = tags("server-gpu-full")
|
||||
platforms = ["linux/amd64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Full (GPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - NVIDIA CUDA GPU build"
|
||||
"ai.noteflow.cuda.version" = CUDA_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Server Targets (GPU - AMD ROCm)
|
||||
# =============================================================================
|
||||
|
||||
# ROCm GPU server (amd64 only)
|
||||
target "server-rocm" {
|
||||
inherits = ["_server-rocm-common"]
|
||||
target = "server"
|
||||
tags = tags("server-rocm")
|
||||
tags = tags_rocm()
|
||||
platforms = ["linux/amd64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server (ROCm)"
|
||||
@@ -268,22 +233,39 @@ target "server-rocm" {
|
||||
}
|
||||
}
|
||||
|
||||
target "server-rocm-full" {
|
||||
inherits = ["_server-rocm-common"]
|
||||
target = "server-full"
|
||||
tags = tags("server-rocm-full")
|
||||
platforms = ["linux/amd64"]
|
||||
# =============================================================================
|
||||
# Development Server Targets (local only, not published)
|
||||
# =============================================================================
|
||||
|
||||
# CPU dev server
|
||||
target "server-dev" {
|
||||
inherits = ["_server-common"]
|
||||
target = "dev"
|
||||
tags = tags_dev("")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Full (ROCm)"
|
||||
"org.opencontainers.image.description" = "NoteFlow gRPC server with all extras - AMD ROCm GPU build"
|
||||
"ai.noteflow.rocm.version" = ROCM_VERSION
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Dev"
|
||||
"org.opencontainers.image.description" = "NoteFlow development server with all extras"
|
||||
}
|
||||
}
|
||||
|
||||
# CUDA GPU dev server
|
||||
target "server-gpu-dev" {
|
||||
inherits = ["_server-gpu-common"]
|
||||
target = "dev"
|
||||
tags = tags_dev("-gpu")
|
||||
platforms = ["linux/amd64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Dev (GPU)"
|
||||
"org.opencontainers.image.description" = "NoteFlow development server - NVIDIA CUDA GPU build"
|
||||
"ai.noteflow.cuda.version" = CUDA_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
# ROCm GPU dev server
|
||||
target "server-rocm-dev" {
|
||||
inherits = ["_server-rocm-common"]
|
||||
target = "server-dev"
|
||||
tags = tags("server-rocm-dev")
|
||||
target = "dev"
|
||||
tags = tags_dev("-rocm")
|
||||
platforms = ["linux/amd64"]
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Server Dev (ROCm)"
|
||||
@@ -293,65 +275,23 @@ target "server-rocm-dev" {
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Client Targets
|
||||
# =============================================================================
|
||||
|
||||
target "client-build" {
|
||||
inherits = ["_client-common"]
|
||||
target = "client-build"
|
||||
tags = tags("client")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Client Build"
|
||||
"org.opencontainers.image.description" = "NoteFlow Tauri desktop client build"
|
||||
}
|
||||
}
|
||||
|
||||
target "client-dev" {
|
||||
inherits = ["_client-common"]
|
||||
target = "client-dev"
|
||||
tags = tags("client-dev")
|
||||
labels = {
|
||||
"org.opencontainers.image.title" = "NoteFlow Client Dev"
|
||||
"org.opencontainers.image.description" = "NoteFlow Tauri client development environment"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Multi-Platform Targets (for CPU images)
|
||||
# =============================================================================
|
||||
|
||||
target "server-multiplatform" {
|
||||
inherits = ["server"]
|
||||
platforms = ["linux/amd64", "linux/arm64"]
|
||||
tags = tags("server-multiplatform")
|
||||
}
|
||||
|
||||
target "server-full-multiplatform" {
|
||||
inherits = ["server-full"]
|
||||
platforms = ["linux/amd64", "linux/arm64"]
|
||||
tags = tags("server-full-multiplatform")
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CI/CD Specific Targets
|
||||
# CI/CD Targets (GHA caching)
|
||||
# =============================================================================
|
||||
|
||||
target "server-ci" {
|
||||
inherits = ["server"]
|
||||
cache-from = [
|
||||
"type=gha"
|
||||
]
|
||||
cache-to = [
|
||||
"type=gha,mode=max"
|
||||
]
|
||||
cache-from = ["type=gha"]
|
||||
cache-to = ["type=gha,mode=max"]
|
||||
}
|
||||
|
||||
target "server-gpu-ci" {
|
||||
inherits = ["server-gpu"]
|
||||
cache-from = [
|
||||
"type=gha"
|
||||
]
|
||||
cache-to = [
|
||||
"type=gha,mode=max"
|
||||
]
|
||||
cache-from = ["type=gha"]
|
||||
cache-to = ["type=gha,mode=max"]
|
||||
}
|
||||
|
||||
target "server-rocm-ci" {
|
||||
inherits = ["server-rocm"]
|
||||
cache-from = ["type=gha"]
|
||||
cache-to = ["type=gha,mode=max"]
|
||||
}
|
||||
|
||||
207
docker-compose.dev.yml
Normal file
207
docker-compose.dev.yml
Normal 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
169
docker-compose.prod.yml
Normal 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
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user