@asd20/ui-next 2.21.4 → 2.21.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.21.6](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.21.5...ui-next-v2.21.6) (2026-08-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * improve relevance of related pages in search results ([ecbcad7](https://github.com/academydistrict20/asd20-ui-next/commit/ecbcad7f5df39273dc6a5b1299b1c94d766d6af3))
9
+
10
+ ## [2.21.5](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.21.4...ui-next-v2.21.5) (2026-08-21)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * fix related page display in ai search results ([dae400e](https://github.com/academydistrict20/asd20-ui-next/commit/dae400e9dedd96c7a2a4afe52ddda8af5aaa1503))
16
+
3
17
  ## [2.21.4](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.21.3...ui-next-v2.21.4) (2026-08-13)
4
18
 
5
19
  ## [2.21.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.21.2...ui-next-v2.21.3) (2026-08-12)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.21.4",
3
+ "version": "2.21.6",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -172,22 +172,24 @@
172
172
  <div
173
173
  v-if="
174
174
  turn.role === 'assistant' &&
175
- turn.id === latestAssistantTurnId
175
+ (getAiTurnRelatedPages(turn).length ||
176
+ turn.relatedPagesStatus === 'pending' ||
177
+ turn.id === latestAssistantTurnId)
176
178
  "
177
179
  class="asd20-site-search__related-pages"
178
180
  >
179
181
  <h2
180
- v-if="aiRelatedPagesPreview.length"
182
+ v-if="getAiTurnRelatedPages(turn).length"
181
183
  class="asd20-site-search__related-pages-title"
182
184
  >
183
185
  Related Pages
184
186
  </h2>
185
187
  <ul
186
- v-if="aiRelatedPagesPreview.length"
188
+ v-if="getAiTurnRelatedPages(turn).length"
187
189
  class="asd20-site-search__related-pages-list"
188
190
  >
189
191
  <li
190
- v-for="page in aiRelatedPagesPreview"
192
+ v-for="page in getAiTurnRelatedPages(turn)"
191
193
  :key="page.id"
192
194
  class="asd20-site-search__related-pages-item"
193
195
  >
@@ -200,15 +202,16 @@
200
202
  </li>
201
203
  </ul>
202
204
  <p
203
- v-if="searchingPages && !aiRelatedPagesPreview.length"
205
+ v-if="turn.relatedPagesStatus === 'pending'"
204
206
  class="asd20-site-search__related-pages-status"
205
207
  >
206
208
  Finding related pages...
207
209
  </p>
208
210
  <asd20-button
211
+ v-if="turn.relatedPagesStatus !== 'pending'"
209
212
  class="asd20-site-search__related-pages-cta"
210
213
  :label="
211
- aiRelatedPagesPreview.length
214
+ getAiTurnRelatedPages(turn).length
212
215
  ? 'View All Pages'
213
216
  : 'Check Pages tab for related results'
214
217
  "
@@ -683,6 +686,7 @@ export default {
683
686
  selectedGroup: null,
684
687
  selectedPerson: null,
685
688
  searchingPages: false,
689
+ pageSearchCompleted: false,
686
690
  searchingFiles: false,
687
691
  searchingPeople: false,
688
692
 
@@ -762,6 +766,8 @@ export default {
762
766
  return (
763
767
  this.hasSubmitted &&
764
768
  !!hasSearchText &&
769
+ this.pageSearchCompleted &&
770
+ !this.searchingAi &&
765
771
  !this.searchingPages &&
766
772
  Array.isArray(this.currentPages) &&
767
773
  this.currentPages.length === 0
@@ -1099,30 +1105,10 @@ export default {
1099
1105
  },
1100
1106
 
1101
1107
  aiRelatedPagesPreview() {
1102
- if (!Array.isArray(this.pageListItems) || !this.pageListItems.length) {
1103
- return []
1104
- }
1105
-
1106
- return this.pageListItems
1107
- .map((item, index) => {
1108
- const page = item && item.page ? item.page : {}
1109
- const title =
1110
- (typeof item.label === 'string' && item.label.trim()) ||
1111
- (typeof page.title === 'string' && page.title.trim()) ||
1112
- `Page ${index + 1}`
1113
- const url =
1114
- (typeof item.link === 'string' && item.link.trim()) ||
1115
- (typeof page.url === 'string' && page.url.trim()) ||
1116
- ''
1117
- const id =
1118
- (typeof page.id === 'string' && page.id.trim()) ||
1119
- (typeof page.slug === 'string' && page.slug.trim()) ||
1120
- `${title}-${index}`
1121
-
1122
- return { id, title, url }
1123
- })
1124
- .filter(page => !!page.url)
1125
- .slice(0, 3)
1108
+ const turn = this.aiTranscript.find(
1109
+ item => item && item.id === this.latestAssistantTurnId
1110
+ )
1111
+ return this.getAiTurnRelatedPages(turn)
1126
1112
  },
1127
1113
  },
1128
1114
 
@@ -1755,6 +1741,7 @@ export default {
1755
1741
  searchMode: this.searchMode || '',
1756
1742
  includeDistrictResults: !!this.resolvedIncludeDistrictResults,
1757
1743
  pages: Array.isArray(this.currentPages) ? this.currentPages : [],
1744
+ pageSearchCompleted: this.pageSearchCompleted,
1758
1745
  files: Array.isArray(this.currentFiles) ? this.currentFiles : [],
1759
1746
  groups: Array.isArray(this.currentGroups) ? this.currentGroups : [],
1760
1747
  people: Array.isArray(this.currentPeople) ? this.currentPeople : [],
@@ -1841,9 +1828,6 @@ export default {
1841
1828
  this.aiConversation = Array.isArray(snapshot.aiConversation)
1842
1829
  ? snapshot.aiConversation
1843
1830
  : []
1844
- this.aiTranscript = Array.isArray(snapshot.aiTranscript)
1845
- ? snapshot.aiTranscript
1846
- : []
1847
1831
  this.aiKeywordsDisplayAnswer = snapshot.aiKeywordsDisplayAnswer || ''
1848
1832
  this.aiKeywordsDisplayPages = snapshot.aiKeywordsDisplayPages || ''
1849
1833
  this.expandedKeywordsTurnId = null
@@ -1860,6 +1844,10 @@ export default {
1860
1844
  ? this.limitWords(targetState.text, 4)
1861
1845
  : ''
1862
1846
  this.keywords = nextKeywords
1847
+ this.pageSearchCompleted =
1848
+ typeof snapshot.pageSearchCompleted === 'boolean'
1849
+ ? snapshot.pageSearchCompleted
1850
+ : !!nextKeywords
1863
1851
 
1864
1852
  this.suppressInputClearReset = true
1865
1853
  this.inputText = ''
@@ -1870,6 +1858,9 @@ export default {
1870
1858
  const groups = Array.isArray(snapshot.groups) ? snapshot.groups : []
1871
1859
  const people = Array.isArray(snapshot.people) ? snapshot.people : []
1872
1860
  this.setSearchResults({ pages, files, groups, people })
1861
+ this.aiTranscript = this.restoreCachedAiTranscript(
1862
+ snapshot.aiTranscript
1863
+ )
1873
1864
 
1874
1865
  if (!skipTabUpdate) {
1875
1866
  const preferredTab = SEARCH_ROUTE_TABS.has(targetState.tab)
@@ -2445,6 +2436,79 @@ export default {
2445
2436
  })
2446
2437
  },
2447
2438
 
2439
+ getAiTurnRelatedPages(turn) {
2440
+ if (!turn || !Array.isArray(turn.relatedPages)) return []
2441
+ return turn.relatedPages
2442
+ },
2443
+
2444
+ buildAiRelatedPagesPreview(items = null) {
2445
+ const previewItems = Array.isArray(items)
2446
+ ? items
2447
+ : Array.isArray(this.currentPages)
2448
+ ? this.currentPages.map(page => ({
2449
+ page,
2450
+ label: page && page.title ? page.title : '',
2451
+ link: page && page.url ? page.url : '',
2452
+ }))
2453
+ : []
2454
+ if (!previewItems.length) return []
2455
+
2456
+ return previewItems
2457
+ .map((item, index) => {
2458
+ const page = item && item.page ? item.page : {}
2459
+ if (page.isNoResultsFallback) return null
2460
+ const title =
2461
+ (typeof item.label === 'string' && item.label.trim()) ||
2462
+ (typeof page.title === 'string' && page.title.trim()) ||
2463
+ `Page ${index + 1}`
2464
+ const url =
2465
+ (typeof item.link === 'string' && item.link.trim()) ||
2466
+ (typeof page.url === 'string' && page.url.trim()) ||
2467
+ ''
2468
+ const id =
2469
+ (typeof page.id === 'string' && page.id.trim()) ||
2470
+ (typeof page.slug === 'string' && page.slug.trim()) ||
2471
+ `${title}-${index}`
2472
+
2473
+ return url ? { id, title, url } : null
2474
+ })
2475
+ .filter(Boolean)
2476
+ .slice(0, 3)
2477
+ },
2478
+
2479
+ restoreCachedAiTranscript(transcript) {
2480
+ const turns = Array.isArray(transcript) ? transcript : []
2481
+ let latestAssistantIndex = -1
2482
+ turns.forEach((turn, index) => {
2483
+ if (turn && turn.role === 'assistant') latestAssistantIndex = index
2484
+ })
2485
+ const legacyLatestPreview = this.buildAiRelatedPagesPreview()
2486
+
2487
+ return turns.map((turn, index) => {
2488
+ if (!turn || turn.role !== 'assistant') return turn
2489
+ const hasOwnedPages = Array.isArray(turn.relatedPages)
2490
+ const relatedPages = hasOwnedPages
2491
+ ? turn.relatedPages
2492
+ .filter(page => page && page.url)
2493
+ .slice(0, 3)
2494
+ .map(page => ({ ...page }))
2495
+ : index === latestAssistantIndex
2496
+ ? legacyLatestPreview
2497
+ : []
2498
+ return { ...turn, relatedPages, relatedPagesStatus: 'complete' }
2499
+ })
2500
+ },
2501
+
2502
+ completeAiTurnRelatedPages(turnId, runId) {
2503
+ if (!turnId || !this.isCurrentSearchRun(runId)) return []
2504
+ const relatedPages = this.buildAiRelatedPagesPreview()
2505
+ this.updateAiTranscriptTurn(turnId, {
2506
+ relatedPages,
2507
+ relatedPagesStatus: 'complete',
2508
+ })
2509
+ return relatedPages
2510
+ },
2511
+
2448
2512
  setAiTurnSearchLogId(turnId, searchLogId) {
2449
2513
  if (!turnId || !searchLogId) return
2450
2514
  this.updateAiTranscriptTurn(turnId, { searchLogId })
@@ -2577,6 +2641,7 @@ export default {
2577
2641
  this.selectedPerson = null
2578
2642
  this.searchingAi = preserveAiLoading ? this.searchingAi : false
2579
2643
  this.searchingPages = false
2644
+ this.pageSearchCompleted = false
2580
2645
  this.searchingFiles = false
2581
2646
  this.searchingPeople = false
2582
2647
  this.activeAiQuestionKey = preserveAiLoading
@@ -2636,6 +2701,7 @@ export default {
2636
2701
  keywords: this.keywords,
2637
2702
  question: this.searchQuestion || this.inputText,
2638
2703
  }
2704
+ if (options.purpose) searchInput.purpose = options.purpose
2639
2705
 
2640
2706
  this.searchingPages = true
2641
2707
  try {
@@ -2649,6 +2715,7 @@ export default {
2649
2715
  if (!this.isCurrentSearchRun(runId)) {
2650
2716
  return this.currentPages
2651
2717
  }
2718
+ this.pageSearchCompleted = true
2652
2719
 
2653
2720
  if (this.keywords && this.keywords.trim() && !keepAnswersTab) {
2654
2721
  this.currentTab = 'Pages'
@@ -3046,6 +3113,8 @@ export default {
3046
3113
  searchLogId: null,
3047
3114
  feedbackValue: null,
3048
3115
  feedbackSubmitting: false,
3116
+ relatedPages: [],
3117
+ relatedPagesStatus: keywordsToUse ? 'pending' : 'complete',
3049
3118
  }
3050
3119
  assistantTurnId = assistantTurn.id
3051
3120
  this.aiTranscript = [...this.aiTranscript, assistantTurn]
@@ -3058,11 +3127,16 @@ export default {
3058
3127
  this.keywordsFromAi = true
3059
3128
  this.skipKeywordWatcher = true
3060
3129
  this.keywords = keywordsToUse
3061
- await this.searchPages({ keepAnswersTab: true, runId })
3130
+ await this.searchPages({
3131
+ keepAnswersTab: true,
3132
+ purpose: 'ai-related-pages',
3133
+ runId,
3134
+ })
3062
3135
  if (!this.isCurrentSearchRun(runId)) return
3063
- relatedPagesForAnalytics = this.getAnalyticsPages()
3064
- .slice(0, 10)
3065
- .map(p => ({ title: p.title || '', url: p.url || '' }))
3136
+ relatedPagesForAnalytics = this.completeAiTurnRelatedPages(
3137
+ assistantTurnId,
3138
+ runId
3139
+ ).map(p => ({ title: p.title || '', url: p.url || '' }))
3066
3140
  this.searchFilesWithGroupOwners({ runId })
3067
3141
  }
3068
3142
 
@@ -3090,6 +3164,8 @@ export default {
3090
3164
  searchLogId: null,
3091
3165
  feedbackValue: null,
3092
3166
  feedbackSubmitting: false,
3167
+ relatedPages: [],
3168
+ relatedPagesStatus: 'complete',
3093
3169
  },
3094
3170
  ]
3095
3171
  this.currentTab = 'Answers'
@@ -3108,29 +3184,12 @@ export default {
3108
3184
  }
3109
3185
 
3110
3186
  // Log AI search analytics
3111
- const logPagesFromPages =
3112
- relatedPagesForAnalytics.length > 0
3113
- ? relatedPagesForAnalytics
3114
- : this.getAnalyticsPages()
3115
- .slice(0, 10)
3116
- .map(p => ({ title: p.title || '', url: p.url || '' }))
3117
- const logPagesFromSources = (
3118
- Array.isArray(this.aiSources) ? this.aiSources : []
3119
- )
3120
- .slice(0, 10)
3121
- .map(source => ({
3122
- title: source && source.title ? source.title : '',
3123
- url: source && source.url ? source.url : '',
3124
- }))
3125
- .filter(source => source.url)
3126
- const logPages =
3127
- logPagesFromPages.length > 0 ? logPagesFromPages : logPagesFromSources
3128
3187
  const logPromise = logSearchAnalytics({
3129
3188
  question: normalizedQuestion,
3130
3189
  searchType: 'ai_chat',
3131
3190
  keywords: this.keywords || '',
3132
3191
  hadResults: !!this.aiAnswer,
3133
- relatedPages: logPages,
3192
+ relatedPages: relatedPagesForAnalytics,
3134
3193
  organizationId:
3135
3194
  this.organization && this.organization.id
3136
3195
  ? this.organization.id