@playpilot/tpi 8.39.0 → 8.40.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.39.0",
3
+ "version": "8.40.0-beta.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -32,6 +32,7 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
32
32
  const foundInjections = validInjections.filter(i => fullText.includes(cleanPhrase(i.sentence)))
33
33
 
34
34
  const failedMessages: Record<string, string> = {}
35
+ const aiInjectionSidsCount: Record<string, number> = {}
35
36
 
36
37
  let injectionIndex = -1 // This index is used in Option 3 below.
37
38
  for (const injection of sortInjections(foundInjections)) {
@@ -40,9 +41,20 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
40
41
  // TODO: REMOVE ME. This is a temporary hack. Popularity should be on participant_details but currently it is only on the injection.
41
42
  if (injection.type === 'participant') injection.participant_details!.popularity ||= injection.popularity
42
43
 
43
- if (injection.type === 'participant' && !injection.manual && !doesParticipantMeetThreshold(mergedInjections, injection.participant_details!)) {
44
- failedMessages[injection.key] = 'Participant does not meet popularity threshold.'
45
- continue
44
+ if (!injection.manual) {
45
+ const previousCount = aiInjectionSidsCount[injection.sid] || 0
46
+
47
+ if (previousCount >= (window.PlayPilotLinkInjections?.config?.unique_injection_count_limit || 10)) {
48
+ failedMessages[injection.key] = 'Injection was withheld because it exceeded the maximum unique count.'
49
+ continue
50
+ } else {
51
+ aiInjectionSidsCount[injection.sid] = previousCount + 1
52
+ }
53
+
54
+ if (injection.type === 'participant' && !doesParticipantMeetThreshold(mergedInjections, injection.participant_details!)) {
55
+ failedMessages[injection.key] = 'Participant does not meet popularity threshold.'
56
+ continue
57
+ }
46
58
  }
47
59
 
48
60
  const elementIndex = elements.findIndex(element => cleanPhrase(element.innerText).includes(cleanPhrase(injection.sentence)))
@@ -52,7 +64,6 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
52
64
 
53
65
  const nodeContainingText = findTextNodeContaining(injection.title, element, ['A'])
54
66
 
55
- // Ignore if the found injection has no node or if it is inside a link.
56
67
  if (!nodeContainingText?.nodeValue || isNodeInLink(nodeContainingText)) {
57
68
  // We check once more where the text was found, this time without ignoring links
58
69
  // so we can determine if the failure was due to it being in a link
@@ -222,6 +233,7 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
222
233
  // so that it is not directly inside of the link.
223
234
  const injectionElement = document.createElement('span')
224
235
  injectionElement.dataset.playpilotInjectionKey = injection.key
236
+ injectionElement.dataset.playpilotInjectionType = injection.type
225
237
 
226
238
  const openInExplore = !!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore
227
239
 
@@ -491,10 +503,11 @@ export function doesParticipantMeetThreshold(injections: LinkInjection[], partic
491
503
  if (participant.popularity === null || participant.popularity === undefined) return true
492
504
 
493
505
  const useArticleAverage = window.PlayPilotLinkInjections?.config?.participant_injection_popularity_threshold_use_article_average ?? true
494
- const defaultThreshold = useArticleAverage ? 25 : 1000
506
+ const defaultThreshold = 0
495
507
  const configThreshold = window.PlayPilotLinkInjections?.config?.participant_injection_popularity_threshold
496
508
  const threshold = configThreshold ?? defaultThreshold
497
509
 
510
+ if (threshold === 0) return true
498
511
  if (!useArticleAverage) return participant.popularity >= threshold
499
512
 
500
513
  const averagePopularity = injections
@@ -115,6 +115,11 @@ export type ConfigResponse = {
115
115
  */
116
116
  participant_injection_popularity_threshold_use_article_average?: boolean
117
117
 
118
+ /**
119
+ * Include a limit of how many times a single titles/participants may appear in one article.
120
+ */
121
+ unique_injection_count_limit?: number
122
+
118
123
  /**
119
124
  * These options are all relevant for the Explore component, which can be inserted as a widget on any page or as a modal.
120
125
  * `explore_navigation_selector` is used to select the navigation element that should be copied and inserted _after_.
@@ -1038,6 +1038,22 @@ describe('injection.ts', () => {
1038
1038
 
1039
1039
  expect(document.querySelectorAll('[data-playpilot-injection-key]')).toHaveLength(2)
1040
1040
  })
1041
+
1042
+ it('Should include injection type on element', () => {
1043
+ document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
1044
+
1045
+ const elements = Array.from(document.body.querySelectorAll('p'))
1046
+ const injections = [
1047
+ { ...generateInjection('This is a sentence with an injection.', 'a sentence'), type: 'participant', participant_details: participants[0] },
1048
+ { ...generateInjection('This is a sentence with an injection.', 'an injection'), type: 'title' },
1049
+ ]
1050
+
1051
+ // @ts-ignore
1052
+ injectLinksInDocument(elements, { aiInjections: injections, manualInjections: [] })
1053
+
1054
+ expect(/** @type {HTMLElement} */ (document.querySelectorAll('[data-playpilot-injection-key]')[0]).dataset.playpilotInjectionType).toBe('participant')
1055
+ expect(/** @type {HTMLElement} */ (document.querySelectorAll('[data-playpilot-injection-key]')[1]).dataset.playpilotInjectionType).toBe('title')
1056
+ })
1041
1057
  })
1042
1058
 
1043
1059
  describe('config.open_tpi_links_in_explore', () => {
@@ -1076,6 +1092,46 @@ describe('injection.ts', () => {
1076
1092
  expect(openModalForInjectedLink).not.toHaveBeenCalled()
1077
1093
  })
1078
1094
  })
1095
+
1096
+ describe('config.unique_injection_count_limit', () => {
1097
+ it('Should not insert another link if the count for links of the same sid is met', () => {
1098
+ window.PlayPilotLinkInjections.config = {
1099
+ unique_injection_count_limit: 2,
1100
+ }
1101
+
1102
+ document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
1103
+
1104
+ const elements = Array.from(document.body.querySelectorAll('p'))
1105
+ const injections = [
1106
+ { ...generateInjection('This is a sentence with an injection.', 'This is'), sid: 'a' },
1107
+ { ...generateInjection('This is a sentence with an injection.', 'a sentence'), sid: 'a' },
1108
+ { ...generateInjection('This is a sentence with an injection.', 'an injection'), sid: 'a' },
1109
+ ]
1110
+
1111
+ injectLinksInDocument(elements, { aiInjections: injections, manualInjections: [] })
1112
+
1113
+ expect(document.querySelectorAll('[data-playpilot-injection-key]')).toHaveLength(2)
1114
+ })
1115
+
1116
+ it('Should disregard limit for manual injections', () => {
1117
+ window.PlayPilotLinkInjections.config = {
1118
+ unique_injection_count_limit: 2,
1119
+ }
1120
+
1121
+ document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
1122
+
1123
+ const elements = Array.from(document.body.querySelectorAll('p'))
1124
+ const aiInjections = [
1125
+ { ...generateInjection('This is a sentence with an injection.', 'This is'), sid: 'a' },
1126
+ { ...generateInjection('This is a sentence with an injection.', 'a sentence'), sid: 'a' },
1127
+ ]
1128
+ const manualInjections = [{ ...generateInjection('This is a sentence with an injection.', 'an injection'), sid: 'a' }]
1129
+
1130
+ injectLinksInDocument(elements, { aiInjections, manualInjections })
1131
+
1132
+ expect(document.querySelectorAll('[data-playpilot-injection-key]')).toHaveLength(3)
1133
+ })
1134
+ })
1079
1135
  })
1080
1136
 
1081
1137
  describe('clearLinkInjections', () => {
@@ -1368,7 +1424,7 @@ describe('injection.ts', () => {
1368
1424
  expect(doesParticipantMeetThreshold(injections, falseyParticipant)).toBe(false)
1369
1425
  })
1370
1426
 
1371
- it('Should return true if threshold is 100, regardless of popularity', () => {
1427
+ it('Should return true if threshold is 0, regardless of popularity', () => {
1372
1428
  window.PlayPilotLinkInjections.config = {
1373
1429
  participant_injection_popularity_threshold: 0,
1374
1430
  }
@@ -1384,5 +1440,18 @@ describe('injection.ts', () => {
1384
1440
  expect(doesParticipantMeetThreshold(injections, truthyParticipant)).toBe(true)
1385
1441
  expect(doesParticipantMeetThreshold(injections, truthyParticipant2)).toBe(true)
1386
1442
  })
1443
+
1444
+ it('Should use threshold of 0 by default', () => {
1445
+ const truthyParticipant = { ...participants[0], popularity: 0 }
1446
+ const truthyParticipant2 = { ...participants[0], popularity: 1 }
1447
+
1448
+ /** @type {import('$lib/types/injection').LinkInjection[]} */
1449
+ const injections = [
1450
+ { ...generateInjection('This is a sentence with an injection.', 'an injection'), type: 'participant', participant_details: truthyParticipant },
1451
+ ]
1452
+
1453
+ expect(doesParticipantMeetThreshold(injections, truthyParticipant)).toBe(true)
1454
+ expect(doesParticipantMeetThreshold(injections, truthyParticipant2)).toBe(true)
1455
+ })
1387
1456
  })
1388
1457
  })