ci: add pre-commit hook and fix linting issues (#545)

* style: deactivate PLC0415 only on part that it's ok

+ re-run pre-commit run --all

* ci: add pre-commit hook

* build: move from yarn to pnpm

* build: move from yarn to pnpm

* build: fix node-version

* ci: install pnpm prior node (?)

* build: update deps and pnpm trying to fix vercel build

* feat: docker www corepack

* style: pre-commit

---------

Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
2025-08-14 20:59:54 -06:00
committed by GitHub
parent b9d891d342
commit 1311714451
26 changed files with 13070 additions and 7803 deletions

24
.github/workflows/pre-commit.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: pre-commit
on:
pull_request:
push:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
cache-dependency-path: "www/pnpm-lock.yaml"
- name: Install dependencies
run: cd www && pnpm install --frozen-lockfile
- uses: pre-commit/action@v3.0.1

View File

@@ -6,7 +6,7 @@ repos:
- id: format - id: format
name: run format name: run format
language: system language: system
entry: bash -c 'cd www && npx prettier --write .' entry: bash -c 'cd www && pnpm format'
pass_filenames: false pass_filenames: false
files: ^www/ files: ^www/

View File

@@ -62,7 +62,7 @@ uv run python -m reflector.tools.process path/to/audio.wav
**Setup:** **Setup:**
```bash ```bash
# Install dependencies # Install dependencies
yarn install pnpm install
# Copy configuration templates # Copy configuration templates
cp .env_template .env cp .env_template .env
@@ -72,19 +72,19 @@ cp config-template.ts config.ts
**Development:** **Development:**
```bash ```bash
# Start development server # Start development server
yarn dev pnpm dev
# Generate TypeScript API client from OpenAPI spec # Generate TypeScript API client from OpenAPI spec
yarn openapi pnpm openapi
# Lint code # Lint code
yarn lint pnpm lint
# Format code # Format code
yarn format pnpm format
# Build for production # Build for production
yarn build pnpm build
``` ```
### Docker Compose (Full Stack) ### Docker Compose (Full Stack)

View File

@@ -79,7 +79,7 @@ Start with `cd www`.
**Installation** **Installation**
```bash ```bash
yarn install pnpm install
cp .env_template .env cp .env_template .env
cp config-template.ts config.ts cp config-template.ts config.ts
``` ```
@@ -89,7 +89,7 @@ Then, fill in the environment variables in `.env` and the configuration in `conf
**Run in development mode** **Run in development mode**
```bash ```bash
yarn dev pnpm dev
``` ```
Then (after completing server setup and starting it) open [http://localhost:3000](http://localhost:3000) to view it in the browser. Then (after completing server setup and starting it) open [http://localhost:3000](http://localhost:3000) to view it in the browser.
@@ -99,7 +99,7 @@ Then (after completing server setup and starting it) open [http://localhost:3000
To generate the TypeScript files from the openapi.json file, make sure the python server is running, then run: To generate the TypeScript files from the openapi.json file, make sure the python server is running, then run:
```bash ```bash
yarn openapi pnpm openapi
``` ```
### Backend ### Backend

View File

@@ -39,7 +39,7 @@ services:
image: node:18 image: node:18
ports: ports:
- "3000:3000" - "3000:3000"
command: sh -c "yarn install && yarn dev" command: sh -c "corepack enable && pnpm install && pnpm dev"
restart: unless-stopped restart: unless-stopped
working_dir: /app working_dir: /app
volumes: volumes:

View File

@@ -5,23 +5,21 @@ Revises: b7df9609542c
Create Date: 2025-08-05 19:36:41.740957 Create Date: 2025-08-05 19:36:41.740957
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
revision: str = "0bc0f3ff0111"
revision: str = '0bc0f3ff0111' down_revision: Union[str, None] = "b7df9609542c"
down_revision: Union[str, None] = 'b7df9609542c'
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
op.add_column('transcript', op.add_column("transcript", sa.Column("webvtt", sa.Text(), nullable=True))
sa.Column('webvtt', sa.Text(), nullable=True)
)
def downgrade() -> None: def downgrade() -> None:
op.drop_column('transcript', 'webvtt') op.drop_column("transcript", "webvtt")

View File

@@ -5,21 +5,20 @@ Revises: 0bc0f3ff0111
Create Date: 2025-08-07 11:27:38.473517 Create Date: 2025-08-07 11:27:38.473517
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa
revision: str = "116b2f287eab"
revision: str = '116b2f287eab' down_revision: Union[str, None] = "0bc0f3ff0111"
down_revision: Union[str, None] = '0bc0f3ff0111'
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
if conn.dialect.name != 'postgresql': if conn.dialect.name != "postgresql":
return return
op.execute(""" op.execute("""
@@ -31,17 +30,17 @@ def upgrade() -> None:
""") """)
op.create_index( op.create_index(
'idx_transcript_search_vector_en', "idx_transcript_search_vector_en",
'transcript', "transcript",
['search_vector_en'], ["search_vector_en"],
postgresql_using='gin' postgresql_using="gin",
) )
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
if conn.dialect.name != 'postgresql': if conn.dialect.name != "postgresql":
return return
op.drop_index('idx_transcript_search_vector_en', table_name='transcript') op.drop_index("idx_transcript_search_vector_en", table_name="transcript")
op.drop_column('transcript', 'search_vector_en') op.drop_column("transcript", "search_vector_en")

View File

@@ -5,17 +5,16 @@ Revises: 116b2f287eab
Create Date: 2025-08-11 19:11:01.316947 Create Date: 2025-08-11 19:11:01.316947
""" """
import json import json
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa
from sqlalchemy import text from sqlalchemy import text
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '8120ebc75366' revision: str = "8120ebc75366"
down_revision: Union[str, None] = '116b2f287eab' down_revision: Union[str, None] = "116b2f287eab"
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -88,7 +87,7 @@ def upgrade() -> None:
# Update the webvtt field # Update the webvtt field
connection.execute( connection.execute(
text("UPDATE transcript SET webvtt = :webvtt WHERE id = :id"), text("UPDATE transcript SET webvtt = :webvtt WHERE id = :id"),
{"webvtt": webvtt_content, "id": transcript_id} {"webvtt": webvtt_content, "id": transcript_id},
) )
updated_count += 1 updated_count += 1
print(f"✓ Updated transcript {transcript_id}") print(f"✓ Updated transcript {transcript_id}")
@@ -104,6 +103,4 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
"""Clear WebVTT field for all transcripts.""" """Clear WebVTT field for all transcripts."""
op.execute( op.execute(text("UPDATE transcript SET webvtt = NULL"))
text("UPDATE transcript SET webvtt = NULL")
)

View File

@@ -104,3 +104,7 @@ select = [
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"reflector/processors/summary/summary_builder.py" = ["E501"] "reflector/processors/summary/summary_builder.py" = ["E501"]
"gpu/**.py" = ["PLC0415"]
"reflector/tools/**.py" = ["PLC0415"]
"migrations/versions/**.py" = ["PLC0415"]
"tests/**.py" = ["PLC0415"]

View File

@@ -21,7 +21,7 @@ from reflector.db.rooms import rooms
from reflector.db.utils import is_postgresql from reflector.db.utils import is_postgresql
from reflector.processors.types import Word as ProcessorWord from reflector.processors.types import Word as ProcessorWord
from reflector.settings import settings from reflector.settings import settings
from reflector.storage import get_transcripts_storage, get_recordings_storage from reflector.storage import get_recordings_storage, get_transcripts_storage
from reflector.utils import generate_uuid4 from reflector.utils import generate_uuid4
from reflector.utils.webvtt import topics_to_webvtt from reflector.utils.webvtt import topics_to_webvtt

View File

@@ -3,6 +3,7 @@ from reflector.settings import settings
def get_transcripts_storage() -> Storage: def get_transcripts_storage() -> Storage:
assert settings.TRANSCRIPT_STORAGE_BACKEND
return Storage.get_instance( return Storage.get_instance(
name=settings.TRANSCRIPT_STORAGE_BACKEND, name=settings.TRANSCRIPT_STORAGE_BACKEND,
settings_prefix="TRANSCRIPT_STORAGE_", settings_prefix="TRANSCRIPT_STORAGE_",

View File

@@ -44,8 +44,6 @@ def range_requests_response(
"""Returns StreamingResponse using Range Requests of a given file""" """Returns StreamingResponse using Range Requests of a given file"""
if not os.path.exists(file_path): if not os.path.exists(file_path):
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
file_size = os.stat(file_path).st_size file_size = os.stat(file_path).st_size

View File

@@ -26,7 +26,7 @@ async def transcript_record_webrtc(
raise HTTPException(status_code=400, detail="Transcript is locked") raise HTTPException(status_code=400, detail="Transcript is locked")
# create a pipeline runner # create a pipeline runner
from reflector.pipelines.main_live_pipeline import PipelineMainLive from reflector.pipelines.main_live_pipeline import PipelineMainLive # noqa: PLC0415
pipeline_runner = PipelineMainLive(transcript_id=transcript_id) pipeline_runner = PipelineMainLive(transcript_id=transcript_id)

3
www/.gitignore vendored
View File

@@ -44,3 +44,6 @@ config.ts
# openapi logs # openapi logs
openapi-ts-error-*.log openapi-ts-error-*.log
# pnpm
.pnpm-store

View File

@@ -1,21 +1,16 @@
#syntax=docker/dockerfile:1.4 #syntax=docker/dockerfile:1.4
FROM node:18-alpine AS base FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# Install dependencies only when needed # Install dependencies only when needed
FROM base AS deps FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY --link package.json pnpm-lock.yaml* ./
# Install dependencies based on the preferred package manager RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
COPY --link package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM base AS builder FROM base AS builder
@@ -29,7 +24,7 @@ COPY --link . .
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED 1
# If using npm comment out above and use below instead # If using npm comment out above and use below instead
RUN yarn build RUN pnpm build
# RUN npm run build # RUN npm run build
# Production image, copy all the files and run next # Production image, copy all the files and run next

View File

@@ -26,7 +26,7 @@ export default function TranscriptBrowser() {
page, page,
selectedSourceKind, selectedSourceKind,
selectedRoomId, selectedRoomId,
searchTerm searchTerm,
); );
const userName = useSessionUser().name; const userName = useSessionUser().name;
const [deletionLoading, setDeletionLoading] = useState(false); const [deletionLoading, setDeletionLoading] = useState(false);
@@ -51,7 +51,7 @@ export default function TranscriptBrowser() {
const handleFilterTranscripts = ( const handleFilterTranscripts = (
sourceKind: SourceKind | null, sourceKind: SourceKind | null,
roomId: string roomId: string,
) => { ) => {
setSelectedSourceKind(sourceKind); setSelectedSourceKind(sourceKind);
setSelectedRoomId(roomId); setSelectedRoomId(roomId);
@@ -107,7 +107,7 @@ export default function TranscriptBrowser() {
setDeletionLoading(false); setDeletionLoading(false);
onCloseDeletion(); onCloseDeletion();
setDeletedItemIds((prev) => setDeletedItemIds((prev) =>
prev ? [...prev, transcriptId] : [transcriptId] prev ? [...prev, transcriptId] : [transcriptId],
); );
}) })
.catch((err) => { .catch((err) => {
@@ -130,7 +130,7 @@ export default function TranscriptBrowser() {
if (status === "already running") { if (status === "already running") {
setError( setError(
new Error("Processing is already running, please wait"), new Error("Processing is already running, please wait"),
"Processing is already running, please wait" "Processing is already running, please wait",
); );
} }
}) })
@@ -141,7 +141,7 @@ export default function TranscriptBrowser() {
}; };
const transcriptToDelete = response?.items?.find( const transcriptToDelete = response?.items?.find(
(i) => i.id === transcriptToDeleteId (i) => i.id === transcriptToDeleteId,
); );
const dialogTitle = transcriptToDelete?.title || "Unnamed Transcript"; const dialogTitle = transcriptToDelete?.title || "Unnamed Transcript";
const dialogDate = transcriptToDelete?.created_at const dialogDate = transcriptToDelete?.created_at

View File

@@ -165,6 +165,8 @@ const TranscriptCreate = () => {
options={supportedLanguages} options={supportedLanguages}
value={targetLanguage} value={targetLanguage}
onChange={onLanguageChange} onChange={onLanguageChange}
onBlur={() => {}}
onFocus={() => {}}
placeholder="Choose your language" placeholder="Choose your language"
/> />
</Box> </Box>

View File

@@ -136,7 +136,6 @@ export default function Player(props: PlayerProps) {
content, content,
drag: false, drag: false,
resize: false, resize: false,
top: 0,
}); });
region.on("click", (e) => { region.on("click", (e) => {
e.stopPropagation(); e.stopPropagation();

View File

@@ -10,7 +10,7 @@ import FileUploadButton from "./fileUploadButton";
import useWebRTC from "./useWebRTC"; import useWebRTC from "./useWebRTC";
import useAudioDevice from "./useAudioDevice"; import useAudioDevice from "./useAudioDevice";
import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react"; import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react";
import { LuScreenShare, LuMic, LuPlay, LuStopCircle } from "react-icons/lu"; import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu";
type RecorderProps = { type RecorderProps = {
transcriptId: string; transcriptId: string;
@@ -253,7 +253,7 @@ export default function Recorder(props: RecorderProps) {
mr={2} mr={2}
onClick={handleRecClick} onClick={handleRecClick}
> >
{isRecording ? <LuStopCircle /> : <LuPlay />} {isRecording ? <LuCircleStop /> : <LuPlay />}
</IconButton> </IconButton>
{!isRecording && (window as any).chrome && ( {!isRecording && (window as any).chrome && (
<IconButton <IconButton

View File

@@ -127,7 +127,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
{isOwner && api && ( {isOwner && api && (
<Select.Root <Select.Root
key={shareMode.value} key={shareMode.value}
value={[shareMode.value]} value={[shareMode.value || ""]}
onValueChange={(e) => updateShareMode(e.value[0])} onValueChange={(e) => updateShareMode(e.value[0])}
disabled={shareLoading} disabled={shareLoading}
collection={shareOptions} collection={shareOptions}
@@ -145,11 +145,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
<Select.Positioner> <Select.Positioner>
<Select.Content> <Select.Content>
{shareOptions.items.map((option) => ( {shareOptions.items.map((option) => (
<Select.Item <Select.Item key={option.value} item={option}>
key={option.value}
item={option}
label={option.label}
>
{option.label} {option.label}
<Select.ItemIndicator /> <Select.ItemIndicator />
</Select.Item> </Select.Item>

View File

@@ -50,7 +50,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
filter: streamItemsFilter, filter: streamItemsFilter,
set: streamItemsSet, set: streamItemsSet,
} = useListCollection({ } = useListCollection({
items: [], initialItems: [] as { label: string; value: string }[],
filter: contains, filter: contains,
}); });
@@ -59,7 +59,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
filter: topicItemsFilter, filter: topicItemsFilter,
set: topicItemsSet, set: topicItemsSet,
} = useListCollection({ } = useListCollection({
items: [], initialItems: [] as { label: string; value: string }[],
filter: contains, filter: contains,
}); });

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useError } from "./errorContext"; import { useError } from "./errorContext";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/nextjs";
const ErrorMessage: React.FC = () => { const ErrorMessage: React.FC = () => {
const { error, setError, humanMessage } = useError(); const { error, setError, humanMessage } = useError();

View File

@@ -11,7 +11,7 @@
"openapi": "openapi-ts" "openapi": "openapi-ts"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react": "^3.22.0", "@chakra-ui/react": "^3.24.2",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
@@ -22,9 +22,8 @@
"@whereby.com/browser-sdk": "^3.3.4", "@whereby.com/browser-sdk": "^3.3.4",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"axios": "^1.8.2", "axios": "^1.8.2",
"chakra-react-select": "^4.9.1", "eslint": "^9.33.0",
"eslint": "^9.9.1", "eslint-config-next": "^14.2.31",
"eslint-config-next": "^14.2.7",
"fontawesome": "^5.6.3", "fontawesome": "^5.6.3",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"jest-worker": "^29.6.2", "jest-worker": "^29.6.2",
@@ -44,8 +43,6 @@
"redlock": "^5.0.0-beta.2", "redlock": "^5.0.0-beta.2",
"sass": "^1.63.6", "sass": "^1.63.6",
"simple-peer": "^9.11.1", "simple-peer": "^9.11.1",
"superagent": "^8.0.9",
"supports-color": "^9.4.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.1.6", "typescript": "^5.1.6",
"wavesurfer.js": "^7.4.2" "wavesurfer.js": "^7.4.2"
@@ -59,5 +56,6 @@
"@types/react": "18.2.20", "@types/react": "18.2.20",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"vercel": "^37.3.0" "vercel": "^37.3.0"
} },
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
} }

12969
www/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,8 @@
"incremental": true, "incremental": true,
"esModuleInterop": true, "esModuleInterop": true,
"target": "esnext", "target": "esnext",
"module": "CommonJS", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",

File diff suppressed because it is too large Load Diff