From 6592ac6a86a8fcbc2e27d99ab8e0cb3a9bc13a17 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 11:12:19 +0800
Subject: [PATCH 01/12] add digest_config column to the user_personalization
table
---
...0174.do.add_digest_config_to_user_personalization.sql | 9 +++++++++
...74.undo.add_digest_config_to_user_personalization.sql | 9 +++++++++
2 files changed, 18 insertions(+)
create mode 100755 packages/db/migrations/0174.do.add_digest_config_to_user_personalization.sql
create mode 100755 packages/db/migrations/0174.undo.add_digest_config_to_user_personalization.sql
diff --git a/packages/db/migrations/0174.do.add_digest_config_to_user_personalization.sql b/packages/db/migrations/0174.do.add_digest_config_to_user_personalization.sql
new file mode 100755
index 000000000..63c0a6f81
--- /dev/null
+++ b/packages/db/migrations/0174.do.add_digest_config_to_user_personalization.sql
@@ -0,0 +1,9 @@
+-- Type: DO
+-- Name: add_digest_config_to_user_personalization
+-- Description: Add digest_config json column to the user_personalization table
+
+BEGIN;
+
+ALTER TABLE omnivore.user_personalization ADD COLUMN digest_config jsonb;
+
+COMMIT;
diff --git a/packages/db/migrations/0174.undo.add_digest_config_to_user_personalization.sql b/packages/db/migrations/0174.undo.add_digest_config_to_user_personalization.sql
new file mode 100755
index 000000000..14e19be15
--- /dev/null
+++ b/packages/db/migrations/0174.undo.add_digest_config_to_user_personalization.sql
@@ -0,0 +1,9 @@
+-- Type: UNDO
+-- Name: add_digest_config_to_user_personalization
+-- Description: Add digest_config json column to the user_personalization table
+
+BEGIN;
+
+ALTER TABLE omnivore.user_personalization DROP COLUMN digest_config;
+
+COMMIT;
From 0639a526763830771e9869a28ac0cc84f427c644 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 11:13:24 +0800
Subject: [PATCH 02/12] add digestConfig to the userPersonalization entity
---
packages/api/src/entity/user_personalization.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/api/src/entity/user_personalization.ts b/packages/api/src/entity/user_personalization.ts
index 788b7050b..b891535fc 100644
--- a/packages/api/src/entity/user_personalization.ts
+++ b/packages/api/src/entity/user_personalization.ts
@@ -56,4 +56,7 @@ export class UserPersonalization {
@Column('json')
fields?: any | null
+
+ @Column('jsonb')
+ digestConfig?: any
}
From 3e84c2f1df09223a41cb2446239483ee09c4e3d6 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 12:17:07 +0800
Subject: [PATCH 03/12] fix tests
---
.../api/src/entity/user_personalization.ts | 2 +-
.../api/src/services/user_personalization.ts | 8 +++---
.../resolvers/user_personalization.test.ts | 28 ++++++-------------
3 files changed, 14 insertions(+), 24 deletions(-)
diff --git a/packages/api/src/entity/user_personalization.ts b/packages/api/src/entity/user_personalization.ts
index b891535fc..d421b3963 100644
--- a/packages/api/src/entity/user_personalization.ts
+++ b/packages/api/src/entity/user_personalization.ts
@@ -55,7 +55,7 @@ export class UserPersonalization {
updatedAt!: Date
@Column('json')
- fields?: any | null
+ fields?: any
@Column('jsonb')
digestConfig?: any
diff --git a/packages/api/src/services/user_personalization.ts b/packages/api/src/services/user_personalization.ts
index cc9cd82c4..40e39e459 100644
--- a/packages/api/src/services/user_personalization.ts
+++ b/packages/api/src/services/user_personalization.ts
@@ -2,22 +2,22 @@ import { DeepPartial } from 'typeorm'
import { UserPersonalization } from '../entity/user_personalization'
import { authTrx } from '../repository'
-export const findUserPersonalization = async (id: string, userId: string) => {
+export const findUserPersonalization = async (userId: string) => {
return authTrx(
(t) =>
t.getRepository(UserPersonalization).findOneBy({
- id,
+ user: { id: userId },
}),
undefined,
userId
)
}
-export const deleteUserPersonalization = async (id: string, userId: string) => {
+export const deleteUserPersonalization = async (userId: string) => {
return authTrx(
(t) =>
t.getRepository(UserPersonalization).delete({
- id,
+ user: { id: userId },
}),
undefined,
userId
diff --git a/packages/api/test/resolvers/user_personalization.test.ts b/packages/api/test/resolvers/user_personalization.test.ts
index 4f940d05f..b8e93d942 100644
--- a/packages/api/test/resolvers/user_personalization.test.ts
+++ b/packages/api/test/resolvers/user_personalization.test.ts
@@ -22,7 +22,7 @@ describe('User Personalization API', () => {
.post('/local/debug/fake-user-login')
.send({ fakeEmail: user.email })
- authToken = res.body.authToken
+ authToken = res.body.authToken as string
})
after(async () => {
@@ -61,25 +61,17 @@ describe('User Personalization API', () => {
res.body.data.setUserPersonalization.updatedUserPersonalization.fields
).to.eql(fields)
- const userPersonalization = await findUserPersonalization(
- res.body.data.setUserPersonalization.updatedUserPersonalization.id,
- user.id
- )
+ const userPersonalization = await findUserPersonalization(user.id)
expect(userPersonalization).to.not.be.null
// clean up
- await deleteUserPersonalization(
- res.body.data.setUserPersonalization.updatedUserPersonalization.id,
- user.id
- )
+ await deleteUserPersonalization(user.id)
})
})
context('when user personalization exists', () => {
- let existingUserPersonalization: UserPersonalization
-
before(async () => {
- existingUserPersonalization = await saveUserPersonalization(user.id, {
+ await saveUserPersonalization(user.id, {
user: { id: user.id },
fields: {
testField: 'testValue',
@@ -89,7 +81,7 @@ describe('User Personalization API', () => {
after(async () => {
// clean up
- await deleteUserPersonalization(existingUserPersonalization.id, user.id)
+ await deleteUserPersonalization(user.id)
})
it('updates the user personalization', async () => {
@@ -106,7 +98,6 @@ describe('User Personalization API', () => {
).to.eql(newFields)
const updatedUserPersonalization = await findUserPersonalization(
- existingUserPersonalization.id,
user.id
)
expect(updatedUserPersonalization?.fields).to.eql(newFields)
@@ -128,7 +119,7 @@ describe('User Personalization API', () => {
after(async () => {
// clean up
- await deleteUserPersonalization(existingUserPersonalization.id, user.id)
+ await deleteUserPersonalization(user.id)
})
const query = `
@@ -150,10 +141,9 @@ describe('User Personalization API', () => {
it('returns the user personalization', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
- expect(res.body.data.getUserPersonalization.userPersonalization).to.eql({
- id: existingUserPersonalization.id,
- fields: existingUserPersonalization.fields,
- })
+ expect(
+ res.body.data.getUserPersonalization.userPersonalization.fields
+ ).to.eql(existingUserPersonalization.fields)
})
})
})
From 7060ae771c7d5544ef5f263a1d0d9c29701ea258 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:17:22 +0800
Subject: [PATCH 04/12] send notifications to the channels defined in digest
config
---
packages/api/src/entity/user.ts | 2 +-
packages/api/src/jobs/ai/create_digest.ts | 110 +++++++++++++++++-----
packages/api/src/services/user.ts | 14 +++
3 files changed, 103 insertions(+), 23 deletions(-)
diff --git a/packages/api/src/entity/user.ts b/packages/api/src/entity/user.ts
index 83526f946..0df30e6ac 100644
--- a/packages/api/src/entity/user.ts
+++ b/packages/api/src/entity/user.ts
@@ -73,5 +73,5 @@ export class User {
() => UserPersonalization,
(userPersonalization) => userPersonalization.user
)
- userPersonalization!: UserPersonalization
+ userPersonalization?: UserPersonalization
}
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index db5650ccb..3a289b5a2 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -12,6 +12,8 @@ import showdown from 'showdown'
import { v4 as uuid } from 'uuid'
import yaml from 'yaml'
import { LibraryItem } from '../../entity/library_item'
+import { User } from '../../entity/user'
+import { env } from '../../env'
import { TaskState } from '../../generated/graphql'
import { redisDataSource } from '../../redis_data_source'
import { Digest, writeDigest } from '../../services/digest'
@@ -19,12 +21,15 @@ import {
findLibraryItemsByIds,
searchLibraryItems,
} from '../../services/library_item'
-import { findDeviceTokensByUserId } from '../../services/user_device_tokens'
+import {
+ findUserAndPersonalization,
+ sendPushNotifications,
+} from '../../services/user'
+import { enqueueSendEmail } from '../../utils/createTask'
import { wordsCount } from '../../utils/helpers'
import { logger } from '../../utils/logger'
import { htmlToMarkdown } from '../../utils/parser'
-import { sendMulticastPushNotifications } from '../../utils/sendNotification'
-import { generateUploadFilePathName, uploadToBucket } from '../../utils/uploads'
+import { uploadToBucket } from '../../utils/uploads'
export type CreateDigestJobSchedule = 'daily' | 'weekly'
@@ -77,6 +82,8 @@ interface RankedTitle {
title: string
}
+type Channel = 'push' | 'email'
+
export const CREATE_DIGEST_JOB = 'create-digest'
export const CRON_PATTERNS = {
// every day at 10:30 UTC
@@ -546,14 +553,86 @@ const uploadSummary = async (
console.timeEnd('uploadSummary')
}
+const sendPushNotification = async (userId: string, digest: Digest) => {
+ const notification = {
+ title: digest.title ?? 'Omnivore digest',
+ body: 'Your digest is ready to listen',
+ }
+
+ await sendPushNotifications(userId, notification, 'reminder')
+}
+
+const sendEmail = async (user: User, digest: Digest) => {
+ const title = digest.title ?? 'Omnivore digest'
+ const html = `
+ ${title}
+
+ Transcript
+ ${digest.content ?? 'Transcript not available'}
+ `
+
+ await enqueueSendEmail({
+ to: user.email,
+ from: env.sender.message,
+ subject: title,
+ html,
+ })
+}
+
+const sendNotifications = async (
+ user: User,
+ channels: Channel[],
+ digest: Digest
+) => {
+ await Promise.all(
+ channels.map(async (channel) => {
+ switch (channel) {
+ case 'push':
+ return sendPushNotification(user.id, digest)
+ case 'email':
+ return sendEmail(user, digest)
+ default:
+ }
+ })
+ )
+}
+
export const createDigest = async (jobData: CreateDigestData) => {
console.time('createDigestJob')
// generate a unique id for the digest if not provided for scheduled jobs
const digestId = jobData.id ?? uuid()
+
+ const user = await findUserAndPersonalization(jobData.userId)
+ if (!user) {
+ logger.error('User not found', { userId: jobData.userId })
+ return writeDigest(jobData.userId, {
+ id: digestId,
+ jobState: TaskState.Failed,
+ })
+ }
+
+ const personalization = user.userPersonalization
+ if (!personalization) {
+ logger.info('User personalization not found')
+ }
+
+ const config = personalization
+ ? (personalization.digestConfig as {
+ model?: string
+ channels?: Channel[]
+ })
+ : undefined
+
+ // default digest
+ let digest: Digest = {
+ id: digestId,
+ jobState: TaskState.Succeeded,
+ }
+
try {
digestDefinition = await fetchDigestDefinition()
- const model = selectModel(digestDefinition.model)
+ const model = selectModel(config?.model || digestDefinition.model)
logger.info(`model: ${model}`)
const candidates = await getCandidatesList(
@@ -562,11 +641,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
)
if (candidates.length === 0) {
logger.info('No candidates found')
- return writeDigest(jobData.userId, {
- id: digestId,
- jobState: TaskState.Succeeded,
- title: 'No articles found',
- })
+ return writeDigest(jobData.userId, digest)
}
// const userProfile = await findOrCreateUserProfile(jobData.userId)
@@ -591,7 +666,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
secondaryVoice: jobData.voices?.[1],
})
const title = generateTitle(summaries)
- const digest: Digest = {
+ digest = {
id: digestId,
title,
content: generateContent(summaries),
@@ -630,19 +705,10 @@ export const createDigest = async (jobData: CreateDigestData) => {
jobState: TaskState.Failed,
})
} finally {
+ // default to push notification
+ const channels = config?.channels ?? ['push']
// send notification
- const tokens = await findDeviceTokensByUserId(jobData.userId)
- if (tokens.length > 0) {
- const message = {
- notification: {
- title: 'Digest ready',
- body: 'Your digest is ready to listen',
- },
- tokens: tokens.map((token) => token.token),
- }
-
- await sendMulticastPushNotifications(jobData.userId, message, 'reminder')
- }
+ await sendNotifications(user, channels, digest)
console.timeEnd('createDigestJob')
}
diff --git a/packages/api/src/services/user.ts b/packages/api/src/services/user.ts
index 52efa08e9..3eabe002a 100644
--- a/packages/api/src/services/user.ts
+++ b/packages/api/src/services/user.ts
@@ -150,3 +150,17 @@ export const sendPushNotifications = async (
return sendMulticastPushNotifications(userId, message, notificationType)
}
+
+export const findUserAndPersonalization = async (id: string) => {
+ return authTrx(
+ (t) =>
+ t.getRepository(User).findOne({
+ where: { id },
+ relations: {
+ userPersonalization: true,
+ },
+ }),
+ undefined,
+ id
+ )
+}
From 20408277d2354dae11407d0237f1bacc87aae872 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:35:55 +0800
Subject: [PATCH 05/12] add digestConfig to the userPersonalization API request
data
---
packages/api/src/entity/user_personalization.ts | 4 ++--
packages/api/src/generated/graphql.ts | 3 +++
packages/api/src/generated/schema.graphql | 2 ++
packages/api/src/schema.ts | 2 ++
packages/api/test/resolvers/user_personalization.test.ts | 6 +++---
5 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/packages/api/src/entity/user_personalization.ts b/packages/api/src/entity/user_personalization.ts
index d421b3963..7fa36b470 100644
--- a/packages/api/src/entity/user_personalization.ts
+++ b/packages/api/src/entity/user_personalization.ts
@@ -55,8 +55,8 @@ export class UserPersonalization {
updatedAt!: Date
@Column('json')
- fields?: any
+ fields?: any | null
@Column('jsonb')
- digestConfig?: any
+ digestConfig?: any | null
}
diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts
index a632e26a8..a63988225 100644
--- a/packages/api/src/generated/graphql.ts
+++ b/packages/api/src/generated/graphql.ts
@@ -3054,6 +3054,7 @@ export enum SetUserPersonalizationErrorCode {
}
export type SetUserPersonalizationInput = {
+ digestConfig?: InputMaybe;
fields?: InputMaybe;
fontFamily?: InputMaybe;
fontSize?: InputMaybe;
@@ -3767,6 +3768,7 @@ export enum UserErrorCode {
export type UserPersonalization = {
__typename?: 'UserPersonalization';
+ digestConfig?: Maybe;
fields?: Maybe;
fontFamily?: Maybe;
fontSize?: Maybe;
@@ -7230,6 +7232,7 @@ export type UserErrorResolvers = {
+ digestConfig?: Resolver, ParentType, ContextType>;
fields?: Resolver, ParentType, ContextType>;
fontFamily?: Resolver, ParentType, ContextType>;
fontSize?: Resolver, ParentType, ContextType>;
diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql
index 815fc9502..1c0800f5b 100644
--- a/packages/api/src/generated/schema.graphql
+++ b/packages/api/src/generated/schema.graphql
@@ -2385,6 +2385,7 @@ enum SetUserPersonalizationErrorCode {
}
input SetUserPersonalizationInput {
+ digestConfig: JSON
fields: JSON
fontFamily: String
fontSize: Int
@@ -3044,6 +3045,7 @@ enum UserErrorCode {
}
type UserPersonalization {
+ digestConfig: JSON
fields: JSON
fontFamily: String
fontSize: Int
diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts
index 8d112d509..30f13320a 100755
--- a/packages/api/src/schema.ts
+++ b/packages/api/src/schema.ts
@@ -1080,6 +1080,7 @@ const schema = gql`
speechRate: String
speechVolume: String
fields: JSON
+ digestConfig: JSON
}
# Query: UserPersonalization
@@ -1122,6 +1123,7 @@ const schema = gql`
speechRate: String
speechVolume: String
fields: JSON
+ digestConfig: JSON
}
# Type: ArticleSavingRequest
diff --git a/packages/api/test/resolvers/user_personalization.test.ts b/packages/api/test/resolvers/user_personalization.test.ts
index b8e93d942..fe04e93f3 100644
--- a/packages/api/test/resolvers/user_personalization.test.ts
+++ b/packages/api/test/resolvers/user_personalization.test.ts
@@ -73,8 +73,8 @@ describe('User Personalization API', () => {
before(async () => {
await saveUserPersonalization(user.id, {
user: { id: user.id },
- fields: {
- testField: 'testValue',
+ digestConfig: {
+ channels: ['email'],
},
})
})
@@ -86,7 +86,7 @@ describe('User Personalization API', () => {
it('updates the user personalization', async () => {
const newFields = {
- testField: 'testValue1',
+ channels: ['push', 'email'],
}
const res = await graphqlRequest(query, authToken, {
From 0692ab04546055d505ee0bf2fcfb8d53a55f0a0d Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:39:12 +0800
Subject: [PATCH 06/12] add chapters to the digest email
---
packages/api/src/jobs/ai/create_digest.ts | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 3a289b5a2..98ee0488f 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -564,10 +564,24 @@ const sendPushNotification = async (userId: string, digest: Digest) => {
const sendEmail = async (user: User, digest: Digest) => {
const title = digest.title ?? 'Omnivore digest'
+ const chapters = digest.chapters ?? []
const html = `
- ${title}
+ ${title}
- Transcript
+ Chapters
+
+ ${chapters
+ .map(
+ (chapter) => `
+ -
+ ${chapter.title}
+
+ `
+ )
+ .join('')}
+
+
+ Transcript
${digest.content ?? 'Transcript not available'}
`
From a9dd2d0d4cbfcb9a2ebd21002e921a6f5d02ef0a Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:43:18 +0800
Subject: [PATCH 07/12] dedupe channels
---
packages/api/src/jobs/ai/create_digest.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 98ee0488f..81c6512f8 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -598,8 +598,10 @@ const sendNotifications = async (
channels: Channel[],
digest: Digest
) => {
+ const deduplicateChannels = [...new Set(channels)]
+
await Promise.all(
- channels.map(async (channel) => {
+ deduplicateChannels.map(async (channel) => {
switch (channel) {
case 'push':
return sendPushNotification(user.id, digest)
From 7b2e336eca1556e70243cc047bfeb27b9850a7ed Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:45:01 +0800
Subject: [PATCH 08/12] log unknown channel
---
packages/api/src/jobs/ai/create_digest.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 81c6512f8..87990ea4d 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -595,8 +595,8 @@ const sendEmail = async (user: User, digest: Digest) => {
const sendNotifications = async (
user: User,
- channels: Channel[],
- digest: Digest
+ digest: Digest,
+ channels: Channel[] = ['push'] // default to push notification
) => {
const deduplicateChannels = [...new Set(channels)]
@@ -608,6 +608,8 @@ const sendNotifications = async (
case 'email':
return sendEmail(user, digest)
default:
+ logger.error('Unknown channel', { channel })
+ return
}
})
)
@@ -721,10 +723,8 @@ export const createDigest = async (jobData: CreateDigestData) => {
jobState: TaskState.Failed,
})
} finally {
- // default to push notification
- const channels = config?.channels ?? ['push']
// send notification
- await sendNotifications(user, channels, digest)
+ await sendNotifications(user, digest, config?.channels)
console.timeEnd('createDigestJob')
}
From 9bac4798b1de55e0fec938ee2c63fe11eebaa797 Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 14:56:45 +0800
Subject: [PATCH 09/12] put omnivore url in each chapter
---
packages/api/src/jobs/ai/create_digest.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 87990ea4d..7c58936e2 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -19,6 +19,7 @@ import { redisDataSource } from '../../redis_data_source'
import { Digest, writeDigest } from '../../services/digest'
import {
findLibraryItemsByIds,
+ getItemUrl,
searchLibraryItems,
} from '../../services/library_item'
import {
@@ -693,7 +694,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
chapters: filteredSummaries.map((item, index) => ({
title: item.libraryItem.title,
id: item.libraryItem.id,
- url: item.libraryItem.originalUrl,
+ url: getItemUrl(item.libraryItem.id),
thumbnail: item.libraryItem.thumbnail ?? undefined,
wordCount: speechFiles[index].wordCount,
})),
From 6a0ea1b0789a40f9e7202d0581623f379686b87a Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 15:15:11 +0800
Subject: [PATCH 10/12] update gcs upload filename
---
packages/api/src/jobs/ai/create_digest.ts | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 7c58936e2..7a86c392e 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -532,7 +532,7 @@ const uploadSummary = async (
console.time('uploadSummary')
logger.info('uploading summaries to gcs')
- const filename = `digest/${userId}/${digest.id}/summaries.json`
+ const filename = `digest/${userId}/${digest.id}.json`
await uploadToBucket(
filename,
Buffer.from(
@@ -589,7 +589,7 @@ const sendEmail = async (user: User, digest: Digest) => {
await enqueueSendEmail({
to: user.email,
from: env.sender.message,
- subject: title,
+ subject: 'Omnivore digest',
html,
})
}
@@ -684,11 +684,11 @@ export const createDigest = async (jobData: CreateDigestData) => {
primaryVoice: jobData.voices?.[0],
secondaryVoice: jobData.voices?.[1],
})
- const title = generateTitle(summaries)
+ const title = generateTitle(filteredSummaries)
digest = {
id: digestId,
title,
- content: generateContent(summaries),
+ content: generateContent(filteredSummaries),
jobState: TaskState.Succeeded,
speechFiles,
chapters: filteredSummaries.map((item, index) => ({
@@ -700,8 +700,8 @@ export const createDigest = async (jobData: CreateDigestData) => {
})),
createdAt: new Date(),
description: '',
- // description: generateDescription(summaries, rankedTopics),
- byline: generateByline(summaries),
+ // description: generateDescription(filteredSummaries, rankedTopics),
+ byline: generateByline(filteredSummaries),
urlsToAudio: [],
model,
}
@@ -710,7 +710,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
// write the digest to redis
writeDigest(jobData.userId, digest),
// upload the summaries to GCS
- uploadSummary(jobData.userId, digest, summaries).catch((error) =>
+ uploadSummary(jobData.userId, digest, filteredSummaries).catch((error) =>
logger.error('uploadSummary error', error)
),
])
From e013963143a77a2c98122f258d34ec448819a29a Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 15:56:19 +0800
Subject: [PATCH 11/12] update email content
---
packages/api/src/jobs/ai/create_digest.ts | 64 ++++++++++++++---------
1 file changed, 39 insertions(+), 25 deletions(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index 7a86c392e..b7c23719a 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -8,6 +8,7 @@ import {
SSMLOptions,
} from '@omnivore/text-to-speech-handler'
import axios from 'axios'
+import { truncate } from 'lodash'
import showdown from 'showdown'
import { v4 as uuid } from 'uuid'
import yaml from 'yaml'
@@ -556,40 +557,50 @@ const uploadSummary = async (
const sendPushNotification = async (userId: string, digest: Digest) => {
const notification = {
- title: digest.title ?? 'Omnivore digest',
- body: 'Your digest is ready to listen',
+ title: 'Omnivore Digest',
+ body: truncate(digest.title, { length: 100 }),
}
await sendPushNotifications(userId, notification, 'reminder')
}
-const sendEmail = async (user: User, digest: Digest) => {
- const title = digest.title ?? 'Omnivore digest'
+const sendEmail = async (
+ user: User,
+ digest: Digest,
+ summaries: RankedItem[]
+) => {
+ const createdAt = digest.createdAt ?? new Date()
+
+ const title = 'Omnivore Digest'
+ const subject = `${title} ${createdAt.toLocaleDateString()}`
+ const subTitle = truncate(digest.title, { length: 200 }).slice(
+ title.length + 1
+ )
+
const chapters = digest.chapters ?? []
+
const html = `
- ${title}
+
+
${subject}
+
${subTitle}
-
Chapters
-
- ${chapters
- .map(
- (chapter) => `
- -
- ${chapter.title}
-
- `
- )
- .join('')}
-
-
- Transcript
-
${digest.content ?? 'Transcript not available'}
- `
+ ${chapters
+ .map(
+ (chapter, index) => `
+
`
+ )
+ .join('')}
+
`
await enqueueSendEmail({
to: user.email,
from: env.sender.message,
- subject: 'Omnivore digest',
+ subject,
html,
})
}
@@ -597,6 +608,7 @@ const sendEmail = async (user: User, digest: Digest) => {
const sendNotifications = async (
user: User,
digest: Digest,
+ summaries: RankedItem[],
channels: Channel[] = ['push'] // default to push notification
) => {
const deduplicateChannels = [...new Set(channels)]
@@ -607,7 +619,7 @@ const sendNotifications = async (
case 'push':
return sendPushNotification(user.id, digest)
case 'email':
- return sendEmail(user, digest)
+ return sendEmail(user, digest, summaries)
default:
logger.error('Unknown channel', { channel })
return
@@ -648,6 +660,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
id: digestId,
jobState: TaskState.Succeeded,
}
+ let filteredSummaries: RankedItem[] = []
try {
digestDefinition = await fetchDigestDefinition()
@@ -677,7 +690,7 @@ export const createDigest = async (jobData: CreateDigestData) => {
const summaries = await summarizeItems(model, selections)
console.timeEnd('summarizeItems')
- const filteredSummaries = filterSummaries(summaries)
+ filteredSummaries = filterSummaries(summaries)
const speechFiles = generateSpeechFiles(filteredSummaries, {
...jobData,
@@ -722,10 +735,11 @@ export const createDigest = async (jobData: CreateDigestData) => {
await writeDigest(jobData.userId, {
id: digestId,
jobState: TaskState.Failed,
+ title: 'Failed to create digest',
})
} finally {
// send notification
- await sendNotifications(user, digest, config?.channels)
+ await sendNotifications(user, digest, filteredSummaries, config?.channels)
console.timeEnd('createDigestJob')
}
From 2fd3b930fe9a35ac7a14e405a9a483375ea54d4f Mon Sep 17 00:00:00 2001
From: Hongbo Wu
Date: Fri, 3 May 2024 16:06:50 +0800
Subject: [PATCH 12/12] use the shortened title as subject
---
packages/api/src/jobs/ai/create_digest.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/api/src/jobs/ai/create_digest.ts b/packages/api/src/jobs/ai/create_digest.ts
index b7c23719a..6ffdd96d4 100644
--- a/packages/api/src/jobs/ai/create_digest.ts
+++ b/packages/api/src/jobs/ai/create_digest.ts
@@ -571,17 +571,17 @@ const sendEmail = async (
) => {
const createdAt = digest.createdAt ?? new Date()
- const title = 'Omnivore Digest'
- const subject = `${title} ${createdAt.toLocaleDateString()}`
+ const prefix = 'Omnivore Digest'
+ const title = `${prefix} ${createdAt.toLocaleDateString()}`
const subTitle = truncate(digest.title, { length: 200 }).slice(
- title.length + 1
+ prefix.length + 1
)
const chapters = digest.chapters ?? []
const html = `
-
${subject}
+ ${title}
${subTitle}
${chapters
@@ -600,7 +600,7 @@ const sendEmail = async (
await enqueueSendEmail({
to: user.email,
from: env.sender.message,
- subject,
+ subject: subTitle,
html,
})
}