mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1404 from omnivore-app/fix/realistic-voice-prefetch
fix/realistic voice prefetch
This commit is contained in:
commit
f55ebce8ef
5 changed files with 50 additions and 41 deletions
|
|
@ -40,7 +40,7 @@ export const optInFeatureResolver = authorized<
|
|||
}
|
||||
}
|
||||
|
||||
const token = signFeatureToken(optIn)
|
||||
const token = signFeatureToken(optIn, claims.uid)
|
||||
|
||||
return {
|
||||
feature: {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { enqueueTextToSpeech } from '../utils/createTask'
|
|||
import { htmlToSpeechFile } from '@omnivore/text-to-speech-handler'
|
||||
import { UserPersonalization } from '../entity/user_personalization'
|
||||
import { ArticleSavingRequestStatus } from '../elastic/types'
|
||||
import { FeatureName, getFeature } from '../services/features'
|
||||
|
||||
const logger = buildLogger('app.dispatch')
|
||||
|
||||
|
|
@ -80,6 +81,11 @@ export function textToSpeechRouter() {
|
|||
},
|
||||
})
|
||||
|
||||
const feature = await getFeature(
|
||||
FeatureName.UltraRealisticVoice,
|
||||
userId
|
||||
)
|
||||
|
||||
for (const utterance of speechFile.utterances) {
|
||||
// enqueue a task to convert text to speech
|
||||
const taskName = await enqueueTextToSpeech({
|
||||
|
|
@ -91,6 +97,8 @@ export function textToSpeechRouter() {
|
|||
isUltraRealisticVoice: true,
|
||||
language: speechFile.language,
|
||||
rate: userPersonalization?.speechRate || '1.1',
|
||||
featureName: feature?.name,
|
||||
grantedAt: feature?.grantedAt,
|
||||
})
|
||||
logger.info('Start Text to speech task', { taskName })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as jwt from 'jsonwebtoken'
|
|||
import { env } from '../env'
|
||||
import { IsNull, Not } from 'typeorm'
|
||||
|
||||
enum FeatureName {
|
||||
export enum FeatureName {
|
||||
UltraRealisticVoice = 'ultra-realistic-voice',
|
||||
}
|
||||
|
||||
|
|
@ -56,10 +56,16 @@ const optInUltraRealisticVoice = async (uid: string): Promise<Feature> => {
|
|||
})
|
||||
}
|
||||
|
||||
export const signFeatureToken = (feature: Feature): string => {
|
||||
export const signFeatureToken = (
|
||||
feature: {
|
||||
name?: string
|
||||
grantedAt?: Date | null
|
||||
},
|
||||
userId: string
|
||||
): string => {
|
||||
return jwt.sign(
|
||||
{
|
||||
uid: feature.user.id,
|
||||
uid: userId,
|
||||
featureName: feature.name,
|
||||
grantedAt: feature.grantedAt ? feature.grantedAt.getTime() / 1000 : null,
|
||||
},
|
||||
|
|
@ -67,3 +73,26 @@ export const signFeatureToken = (feature: Feature): string => {
|
|||
{ expiresIn: '1y' }
|
||||
)
|
||||
}
|
||||
|
||||
export const isOptedIn = async (
|
||||
name: FeatureName,
|
||||
uid: string
|
||||
): Promise<boolean> => {
|
||||
const feature = await getRepository(Feature).findOneBy({
|
||||
user: { id: uid },
|
||||
name,
|
||||
grantedAt: Not(IsNull()),
|
||||
})
|
||||
|
||||
return !!feature
|
||||
}
|
||||
|
||||
export const getFeature = async (
|
||||
name: FeatureName,
|
||||
uid: string
|
||||
): Promise<Feature | null> => {
|
||||
return getRepository(Feature).findOneBy({
|
||||
user: { id: uid },
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { searchPages } from '../elastic/pages'
|
||||
import { Page, PageType } from '../elastic/types'
|
||||
import { SortBy, SortOrder } from '../utils/search'
|
||||
import { FeatureName, isOptedIn } from './features'
|
||||
|
||||
/*
|
||||
* We should not synthesize the page when:
|
||||
** 1. User has no recent listens the last 30 days
|
||||
** 2. User has a recent listen but the page was saved after the listen
|
||||
* We should synthesize the page when user is opted in to the feature
|
||||
*/
|
||||
export const shouldSynthesize = async (
|
||||
userId: string,
|
||||
|
|
@ -16,31 +13,5 @@ export const shouldSynthesize = async (
|
|||
return false
|
||||
}
|
||||
|
||||
if (process.env.TEXT_TO_SPEECH_BETA_TEST) {
|
||||
return true
|
||||
}
|
||||
|
||||
const [recentListenedPage, count] = (await searchPages(
|
||||
{
|
||||
dateFilters: [
|
||||
{
|
||||
field: 'listenedAt',
|
||||
startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
],
|
||||
sort: {
|
||||
by: SortBy.LISTENED,
|
||||
order: SortOrder.DESCENDING,
|
||||
},
|
||||
size: 1,
|
||||
},
|
||||
userId
|
||||
)) || [[], 0]
|
||||
if (count === 0) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
!!recentListenedPage[0].listenedAt &&
|
||||
page.savedAt < recentListenedPage[0].listenedAt
|
||||
)
|
||||
return isOptedIn(FeatureName.UltraRealisticVoice, userId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { google } from '@google-cloud/tasks/build/protos/protos'
|
|||
import { IntegrationType } from '../entity/integration'
|
||||
import { promisify } from 'util'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import { signFeatureToken } from '../services/features'
|
||||
import View = google.cloud.tasks.v2.Task.View
|
||||
|
||||
const logger = buildLogger('app.dispatch')
|
||||
|
|
@ -347,6 +348,8 @@ export const enqueueTextToSpeech = async ({
|
|||
isUltraRealisticVoice = false,
|
||||
language,
|
||||
rate,
|
||||
featureName,
|
||||
grantedAt,
|
||||
}: {
|
||||
userId: string
|
||||
speechId: string
|
||||
|
|
@ -360,6 +363,8 @@ export const enqueueTextToSpeech = async ({
|
|||
isUltraRealisticVoice?: boolean
|
||||
language?: string
|
||||
rate?: string
|
||||
featureName?: string
|
||||
grantedAt?: Date | null
|
||||
}): Promise<string> => {
|
||||
const { GOOGLE_CLOUD_PROJECT } = process.env
|
||||
const payload = {
|
||||
|
|
@ -372,11 +377,7 @@ export const enqueueTextToSpeech = async ({
|
|||
language,
|
||||
rate,
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const token = await signToken({ uid: userId }, env.server.jwtSecret, {
|
||||
expiresIn: '1h',
|
||||
})
|
||||
const token = signFeatureToken({ name: featureName, grantedAt }, userId)
|
||||
const taskHandlerUrl = `${env.queue.textToSpeechTaskHandlerUrl}?token=${token}`
|
||||
// If there is no Google Cloud Project Id exposed, it means that we are in local environment
|
||||
if (env.dev.isLocal || !GOOGLE_CLOUD_PROJECT) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue