Skip to content

Commit

Permalink
Move engine to settings UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jptrsn committed Feb 2, 2025
1 parent 0e260fc commit f14a5b7
Show file tree
Hide file tree
Showing 17 changed files with 123 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
</button>

<!-- Trigger recognition buttons -->
<ng-container *ngIf="activeRoute() !== '/stream'">
<app-recognition-enable></app-recognition-enable>
<ng-container *ngIf="activeRoute() !== '/stream'" [ngSwitch]="showRecordButton()">
<app-audio-input-enable *ngSwitchCase="true"></app-audio-input-enable>
<app-recognition-enable *ngSwitchCase="false"></app-recognition-enable>
<button *ngIf="!isActive() && activeRoute() === '/'" class="btn btn-circle btn-ghost tooltip tooltip-bottom" [ngClass]="{'btn-sm': windowControlsOverlay()}" [attr.data-tip]="'ROUTES.stream' | translate" routerLink="stream">
<ng-icon name="tablerBuildingBroadcastTower"></ng-icon>
</button>
Expand Down
9 changes: 4 additions & 5 deletions packages/client/src/app/effects/audio-stream.effects.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { catchError, of, switchMap, tap } from "rxjs";
import { catchError, map, of, switchMap } from "rxjs";
import { AudioStreamActions } from '../models/audio-stream.model';
import { RecognitionActions } from '../actions/recogntion.actions';
import { MediaService } from "../modules/media/services/media.service";

@Injectable()
Expand All @@ -14,17 +13,17 @@ export class AudioStreamEffects {
this.actions$.pipe(
ofType(AudioStreamActions.connectStream),
switchMap((props) => this.mediaService.getMediaStream(props.id)),
switchMap((streamId: string) => [AudioStreamActions.connectStreamSuccess({ id: streamId }), RecognitionActions.connect({id: streamId})]),
map((streamId: string) => AudioStreamActions.connectStreamSuccess({ id: streamId })),
catchError((error: {message: string}) => of(AudioStreamActions.connectStreamFailure({error: error.message}))),
)
)

disconnectStream$ = createEffect(() =>
this.actions$.pipe(
ofType(AudioStreamActions.disconnectStream),
switchMap((props) => {
map((props) => {
const disconnectedId = this.mediaService.disconnectStream(props.id);
return [RecognitionActions.disconnect(props), AudioStreamActions.disconnectStreamSuccess({id: disconnectedId})]
return AudioStreamActions.disconnectStreamSuccess({id: disconnectedId})
})
))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<button
class="btn btn-ghost btn-circle tooltip tooltip-left"
[ngClass]="{'btn-accent': disconnected(), 'btn-primary': connected(), 'btn-error': !!error(), 'btn-sm': small()}"
class="btn btn-circle tooltip tooltip-left"
[ngClass]="{'btn-xs': connected(), 'btn-error': error(), 'btn-accent': disconnected(), 'btn-sm': small()}"
(click)="toggleState()"
appBackgroundMagnitude
[magnitude]="vol()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Store, select } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { filter, of, switchMap } from 'rxjs';
import { AppState } from '../../../../models/app.model';
import { AudioStreamState, AudioStreamStatus } from '../../../../models/audio-stream.model';
import { AudioStreamActions, AudioStreamState, AudioStreamStatus } from '../../../../models/audio-stream.model';
import { errorSelector, windowControlsOverlaySelector } from '../../../../selectors/app.selector';
import { selectAudioStream } from '../../../../selectors/audio-stream.selector';
import { MediaService } from '../../services/media.service';
Expand Down Expand Up @@ -46,11 +46,13 @@ export class AudioInputEnableComponent {
toggleState(): void {
if (this.connected() || this.error()) {
this.store.dispatch(RecognitionActions.disconnect({id: this.streamState().id}))
this.store.dispatch(AudioStreamActions.disconnectStream({id: this.streamState().id}))
} else {
if (this.router.url !== '/') {
this.router.navigate(['/'])
}
this.store.dispatch(RecognitionActions.connect({id: this.streamState().id}))
this.store.dispatch(AudioStreamActions.connectStream({id: this.streamState().id}))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="card bg-base-100 shadow-xl flex flex-col max-w-xl" *ngIf="isLoggedIn(); else loginRequired">
<div class="card-body">
<div class="card-title"><span translate>SETTINGS.PROVIDER.title</span></div>
<div class="text-sm mb-3" translate>SETTINGS.PROVIDER.description</div>

<app-recognition-engine-select></app-recognition-engine-select>
<ng-container *ngIf="provider() as p">
<div class="text-md mt-3">{{'SETTINGS.PROVIDER.' + p + '.label' | translate}}</div>
<p>{{'SETTINGS.PROVIDER.' + p + '.description' | translate }}</p>
<div class="flex flex-row flex-wrap justify-between">
<div class="text-sm">{{'SETTINGS.PROVIDER.' + p + '.cost' | translate }}</div>
<div class="text-sm" *ngIf="p !== 'web'"><span translate>SETTINGS.PROVIDER.balance</span>: {{balance()}}</div>
</div>
</ng-container>
</div>
</div>

<ng-template #loginRequired>
<div class="badge badge-warning p-3 cursor-pointer" [routerLink]="['..','auth']" translate>SETTINGS.SHARING.loginRequired</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecognitionEngineComponent } from './recognition-engine.component';

describe('RecognitionEngineComponent', () => {
let component: RecognitionEngineComponent;
let fixture: ComponentFixture<RecognitionEngineComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RecognitionEngineComponent],
}).compileComponents();

fixture = TestBed.createComponent(RecognitionEngineComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, computed, Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { Store, select } from '@ngrx/store';
import { AppState } from '../../../../models/app.model';
import { RecognitionEngineState } from '../../../../models/recognition.model';
import { selectRecognitionEngine } from '../../../../selectors/recognition.selector';
import { map } from 'rxjs';
import { selectUserBalance } from '../../../../selectors/user.selector';
import { selectUserLoggedIn } from '../../../../selectors/auth.selectors';

@Component({
selector: 'app-recognition-engine',
templateUrl: './recognition-engine.component.html',
styleUrls: ['./recognition-engine.component.scss'],
})
export class RecognitionEngineComponent {
public provider: Signal<RecognitionEngineState['provider'] | undefined>;
public balance: Signal<number | undefined>;
public isLoggedIn: Signal<boolean | undefined>;
constructor(private store: Store<AppState>) {
this.provider = toSignal(this.store.pipe(
select(selectRecognitionEngine),
map((e) => e?.provider )
));

this.isLoggedIn = toSignal(this.store.select(selectUserLoggedIn));
const balance = toSignal(this.store.select(selectUserBalance));
this.balance = computed(() => this.isLoggedIn() ? balance() : undefined)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
<app-broadcast-settings *ngSwitchCase="'sharing'" @fadeInOnEnter></app-broadcast-settings>
<app-obs-studio-settings *ngSwitchCase="'obs'" @fadeInOnEnter></app-obs-studio-settings>
<app-transcription-settings *ngSwitchCase="'transcription'" @fadeInOnEnter></app-transcription-settings>
<app-recognition-engine *ngSwitchCase="'engine'" @fadeInOnEnter></app-recognition-engine>
</div>

</div>

<app-unsaved-changes-dialog [openModal]="showUnsavedChangesModal" (afterClosed)="modalClosed($event)"></app-unsaved-changes-dialog>
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ export class SettingsComponent {
'sync',
'sharing',
'obs',
'engine'
];

public tabIcons: {[key: string]: string} = {
'appearance': 'heroSwatch',
'transcription': 'heroDocumentText',
'sync': 'heroArrowsRightLeft',
'sharing': 'heroShare',
'obs': 'obsStudioLogo'
'obs': 'obsStudioLogo',
'engine': 'heroWrenchScrewdriver'
}

constructor(private fb: FormBuilder,
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/app/modules/settings/settings.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { ObsStudioSettingsComponent } from './components/obs-studio-settings/obs
import { TranscriptionSettingsComponent } from './components/transcription-settings/transcription-settings.component';
import { TranscriptEnabledComponent } from './components/transcript-enabled/transcript-enabled.component';
import { DialectSelectorComponent } from './components/dialect-selector/dialect-selector.component';
import { RecognitionEngineComponent } from './components/recognition-engine/recognition-engine.component';
import { RecognitionEngineSelectComponent } from '../../components/recognition-engine-select/recognition-engine-select.component';

@NgModule({
declarations: [
Expand All @@ -50,6 +52,7 @@ import { DialectSelectorComponent } from './components/dialect-selector/dialect-
TranscriptionSettingsComponent,
TranscriptEnabledComponent,
DialectSelectorComponent,
RecognitionEngineComponent,
],
imports: [
CommonModule,
Expand All @@ -65,6 +68,7 @@ import { DialectSelectorComponent } from './components/dialect-selector/dialect-
...Icons,
}),
ClipboardModule,
RecognitionEngineSelectComponent,
],
})
export class SettingsModule {}
3 changes: 1 addition & 2 deletions packages/client/src/app/modules/settings/settings.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Routes } from '@angular/router'
import { SettingsComponent } from './components/settings/settings.component'
import { unsavedChangesGuard } from '../../guards/unsaved-changes/unsaved-changes.guard'

export const routes: Routes = [
{ path: '', component: SettingsComponent, canDeactivate: [ unsavedChangesGuard ] },
{ path: '', component: SettingsComponent },
]
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
<div class="flex flex-col items-center justify-center basis-full overflow-y-auto p-6 gap-6 flex-grow-0 bg-base-300 text-base-content">
<!-- Tabs header -->
<form class="flex flex-row items-center justify-center gap-3 self-stretch tabs-bordered">
<div class="relative border-b-2" [ngClass]="{'text-secondary border-secondary': tabIndex() === i}" *ngFor="let name of tabNames, index as i">
<input [formControl]="tabsControl" type="radio" role="tab" class="tab" [attr.aria-label]="'USER.TABS.' + name | translate" [value]="i" [id]="name">
<label [for]="name" class="absolute top-0 left-0 right-0 cursor-pointer sm:!hidden flex items-center justify-center">
<ng-icon [name]="tabIcons[name]" size="24"></ng-icon>
</label>
</div>
</form>

<div class="text-lg text-bold text-secondary mt-2 sm:hidden">
{{ 'USER.TABS.' + tabNames[tabIndex()] | translate }}
</div>

<div class="basis-full overflow-y-auto p-3" [ngSwitch]="tabNames[tabIndex()]">
<app-user-profile class="sm:basis-auto basis-full" *ngSwitchCase="'account'"></app-user-profile>
<app-provider class="sm:basis-auto basis-full" *ngSwitchCase="'provider'"></app-provider>
</div>
<app-user-profile class="sm:basis-auto basis-full"></app-user-profile>

<div class="flex flex-row gap-3" *ngIf="!loggedIn()">
<button class="btn btn-primary" [routerLink]="['..']" translate>ROUTES.home</button>
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/app/reducers/user.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface UserState {
uiSettings?: SettingsState;
rooms?: UserRoom[];
error?: string;
creditBalance?: number;
creditAcquisitions?: CreditAdd[];
creditExpenditures?: CreditExpenditure[];
}
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/app/selectors/user.selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ export const selectUserSupportProfile = createSelector(
export const selectUserContributes = createSelector(
selectUserState,
(state) => !!(state.supporter?.amountCents)
)

export const selectUserBalance = createSelector(
selectUserState,
(state) => state.creditBalance
)
40 changes: 21 additions & 19 deletions packages/client/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@
"sync": "Sync",
"sharing": "Sharing",
"obs": "OBS Studio",
"transcription": "Transcript"
"transcription": "Transcript",
"engine": "Engine"
},
"UI": {
"t1": "Preview",
Expand Down Expand Up @@ -346,7 +347,24 @@
"localDataWarning": "All data will be stored locally on your device, using an encryption key that is unique to your account. Remember to sign in before starting your captions to be sure the transcript is saved.",
"encryptionWarning": "If you choose to delete your account, you will delete all saved data and encryption keys",
"loginRequired": "You must be signed-in to configure transcriptions."
}
},
"PROVIDER": {
"title": "Recognition API Provider",
"description": "Select which service providder you would like use to securely process audio and return results. Different engines will vary in speed, quality, and cost. We do our best to bring you as much choice as possible while keeping things as free as we can.",
"balance": "Your credits",
"web": {
"label": "Browser (Chrome/Safari)",
"description": "Using the browser provider, Zip Captions will send audio data from your device directly to your browser provider using the Web Speech API.",
"cost": "Cost: always free",
"link": "https://learn.microsoft.com/en-us/javascript/api/overview/azure/microsoft-cognitiveservices-speech-sdk-readme?view=azure-node-latest"
},
"azure": {
"label": "Microsoft Azure Cognitive Services",
"description": "Using the Azure Cognitive Services provider, Zip Captions will send audio data from your device directly to Microsoft's API using a secure web socket.",
"cost": "Cost: 60 credits per minute",
"link": "https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition"
}
}
},
"STREAM": {
"TABS": {
Expand Down Expand Up @@ -620,23 +638,7 @@
"usernameLabel": "Patreon user name",
"startDateLabel": "Joined",
"thanksMessage": "Thank you for supporting us - we couldn't do this without your help!"
},
"PROVIDER": {
"title": "Recognition Provider",
"description": "Select which service you would like to process audio and return results. Different engines will vary in speed, quality, and cost. We do our best to bring you as much choice as possible while keeping things as free as we can.",
"web": {
"label": "Browser (Chrome/Safari)",
"description": "Using the browser provider, Zip Captions will send audio data from your device directly to your browser provider using the Web Speech API.",
"cost": "free",
"link": "https://learn.microsoft.com/en-us/javascript/api/overview/azure/microsoft-cognitiveservices-speech-sdk-readme?view=azure-node-latest"
},
"azure": {
"label": "Microsoft Azure Cognitive Services",
"description": "Using the Azure Cognitive Services provider, Zip Captions will send audio data from your device directly to Microsoft's API using a secure web socket.",
"cost": "60 credits per minute",
"link": "https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition"
}
}
}
},
"TRANSCRIPT": {
"titlePlural": "Transcripts",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/app/modules/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class UserController {
googleConnected: !!user.googleId,
azureConnected: !!user.msId,
syncUiSettings: user.syncUiSettings,
creditBalance: user.creditBalance
creditBalance: user.creditBalance || 0
}
return userProfile;
}
Expand Down

0 comments on commit f14a5b7

Please sign in to comment.