Skip to content

Commit

Permalink
Engine UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jptrsn committed Feb 4, 2025
1 parent f14a5b7 commit e004848
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<button
class="btn btn-circle tooltip tooltip-left"
class="btn btn-circle tooltip tooltip-left relative"
[ngClass]="{'btn-xs': connected(), 'btn-error': error(), 'btn-accent': disconnected(), 'btn-sm': small()}"
(click)="toggleState()"
appBackgroundMagnitude
[magnitude]="vol()"
[attr.data-tip]="connected() ? ('LABELS.stop' | translate) : ('LABELS.listen' | translate)"
>
<ng-icon *ngIf="provider() !== 'web' && !connected()" class="absolute -top-2 -right-2 text-success rounded-full" size="24" name="heroSparkles"></ng-icon>
<ng-icon *ngIf="error(); else micEnabled" class="tooltip tooltip-warning tooltip-left" [attr.data-tip]="error()" name="heroExclamationTriangle"></ng-icon>
<ng-template #micEnabled>
<ng-container *ngIf="connected(); else inactive">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { filter, of, switchMap } from 'rxjs';
import { filter, map, of, switchMap } from 'rxjs';
import { AppState } from '../../../../models/app.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';
import { RecognitionActions } from '../../../../actions/recogntion.actions';
import { RecognitionEngineState } from '../../../../models/recognition.model';
import { selectRecognitionEngine } from '../../../../selectors/recognition.selector';

@Component({
selector: 'app-audio-input-enable',
Expand All @@ -23,6 +25,7 @@ export class AudioInputEnableComponent {
public disconnected: Signal<boolean | undefined>;
public error: Signal<string | undefined>;
public small: Signal<boolean | undefined>;
public provider: Signal<RecognitionEngineState['provider'] | undefined>;

constructor(private store: Store<AppState>,
private mediaService: MediaService,
Expand All @@ -31,6 +34,10 @@ export class AudioInputEnableComponent {
this.streamState = toSignal(this.store.pipe(select(selectAudioStream))) as Signal<AudioStreamState>;
this.connected = computed(() => this.streamState().status === AudioStreamStatus.connected);
this.disconnected = computed(() => this.streamState().status === AudioStreamStatus.disconnected);
this.provider = toSignal(this.store.pipe(
select(selectRecognitionEngine),
map((e) => e?.provider )
));

const appError = toSignal(this.store.pipe(
select(errorSelector),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
<div class="card bg-base-100 shadow-xl flex flex-col max-w-xl" *ngIf="isLoggedIn(); else loginRequired">
<div class="card-body">
<form 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>
<label class="form-control" [formGroup]="group">
<span class="text-accent label" translate>LABELS.provider</span>
<select class="select select-secondary select-lg mt-1 w-full" formControlName="provider">
<option *ngFor="let p of providers" [value]="p.value">{{'SETTINGS.PROVIDER.' + p.value + '.label' | translate}}</option>
</select>
</label>

<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 class="text-sm" [ngClass]="{'text-error': group.hasError('credits')}" *ngIf="p !== 'web'"><span translate>SETTINGS.PROVIDER.balance</span>: {{balance()}}</div>
</div>
</ng-container>
</div>
<div *ngIf="group.hasError('credits')" class="text-sm flex flex-row flex-wrap justify-between">
<span class="text-error" translate>SETTINGS.PROVIDER.acquire</span><a [href]="patreonUrl" target="_blank">{{patreonUrl}}</a>
</div>
<button class="btn btn-primary" type="button" (click)="setProvider()" translate>BUTTONS.save</button>
</form>
</div>

<ng-template #loginRequired>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ 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 { map, startWith } from 'rxjs';
import { selectUserBalance } from '../../../../selectors/user.selector';
import { selectUserLoggedIn } from '../../../../selectors/auth.selectors';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { RecognitionActions } from '../../../../actions/recogntion.actions';

@Component({
selector: 'app-recognition-engine',
Expand All @@ -17,15 +19,44 @@ 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(
public patreonUrl = 'https://patreon.com/zipcaptions';
public group: FormGroup<{provider: FormControl<RecognitionEngineState['provider'] | null | undefined>}>;
public providers: { value: RecognitionEngineState['provider'], paid: boolean }[] = [
{ value: 'web', paid: false },
{ value: 'azure', paid: true}
]
constructor(private store: Store<AppState>,
private fb: FormBuilder
) {
const provider = toSignal(this.store.pipe(
select(selectRecognitionEngine),
map((e) => e?.provider )
));

this.group = this.fb.group({
provider: this.fb.control(provider())
});

const formValue = toSignal(this.group.controls['provider'].valueChanges);
this.provider = computed(() => {
return formValue() || provider()
})

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

setProvider(): void {
this.group.updateValueAndValidity();
const {provider} = this.group.getRawValue();
if (provider) {
if (provider !== 'web' && !this.balance()) {
this.group.setErrors({'credits': true});
this.group.markAllAsTouched();
} else {
this.store.dispatch(RecognitionActions.setEngine({ engine: provider }))}
}
}

}
4 changes: 2 additions & 2 deletions packages/client/src/app/selectors/user.selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ export const selectUserContributes = createSelector(
)

export const selectUserBalance = createSelector(
selectUserState,
(state) => state.creditBalance
selectUserProfile,
(state) => state?.creditBalance
)
1 change: 1 addition & 0 deletions packages/client/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@
"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",
"acquire": "To get credits, you can support us on Patreon, or request accommodations (coming soon)",
"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.",
Expand Down
5 changes: 3 additions & 2 deletions packages/server/src/app/modules/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ export class UserController {

@Get('profile/:id')
@UseGuards(JwtAuthGuard)
@UseInterceptors(CustomCacheInterceptor)
@CacheKey(UserController.PROFILE_CACHE_KEY)
@NoCache()
// @UseInterceptors(CustomCacheInterceptor)
// @CacheKey(UserController.PROFILE_CACHE_KEY)
async getUser(@Req() req, @Param() params: { id: string }): Promise<UserProfile> {
this._validateParam(req, params);
const user = await this.userService.findOne({id: req.user.id});
Expand Down
1 change: 1 addition & 0 deletions packages/shared-ui/src/lib/vectors/vectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export {
heroDocumentText,
heroUser,
heroWrenchScrewdriver,
heroSparkles
} from '@ng-icons/heroicons/outline';

export {
Expand Down

0 comments on commit e004848

Please sign in to comment.