Refactor to pull all text enteries into single location
This commit is contained in:
+3
-93
@@ -1,3 +1,5 @@
|
||||
import { getLLMCopy } from '@/lib/profile-content'
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
@@ -17,99 +19,7 @@ export function isLLMAvailable(): boolean {
|
||||
}
|
||||
|
||||
export function buildSystemPrompt(): string {
|
||||
return `You are a helpful assistant on Andy Charlwood's portfolio website. Answer questions about Andy's professional background using ONLY the information below.
|
||||
|
||||
## Profile
|
||||
Andy Charlwood — MPharm, GPhC Registered Pharmacist. Norwich, UK.
|
||||
Healthcare leader combining clinical pharmacy with Python, SQL, and data analytics (self-taught). Leading population health analytics for NHS Norfolk & Waveney ICB, serving 1.2M people. Specialises in prescribing data at scale — financial modelling, algorithm design, pathway development. Identified efficiency programmes worth £14.6M+ through automated analysis.
|
||||
|
||||
## Employment Timeline (IMPORTANT)
|
||||
- **NHS employment**: May 2022–present (all roles at NHS Norfolk & Waveney ICB). Total NHS service: ~4 years.
|
||||
- **Private sector**: Nov 2017–May 2022 at Tesco PLC (community pharmacy). This was NOT NHS employment.
|
||||
- GPhC registration (Aug 2016) is a professional licence, NOT an employer or NHS role.
|
||||
|
||||
## Career History
|
||||
|
||||
### [exp-interim-head-2025] Interim Head, Population Health & Data Analysis
|
||||
NHS Norfolk & Waveney ICB | May–Nov 2025
|
||||
Led population health initiatives and data-driven medicines optimisation, reporting to Associate Director of Pharmacy with accountability to CMO.
|
||||
- Identified £14.6M efficiency programme; achieved over-target performance by October 2025
|
||||
- Built Python switching algorithm: real-world GP prescribing data, 14,000 patients, £2.6M annual savings (£2M on target), compressed months into 3 days
|
||||
- Novel GP payment system linking rewards to savings; 50% prescribing reduction within 2 months
|
||||
- Presented to CMO bimonthly; led transformation to patient-level SQL analytics
|
||||
|
||||
### [exp-deputy-head-2024] Deputy Head, Population Health & Data Analysis
|
||||
NHS Norfolk & Waveney ICB | Jul 2024–Present (substantive role)
|
||||
Data analytics strategy for medicines optimisation from real-world GP prescribing data.
|
||||
- Managed £220M prescribing budget with forecasting models for proactive financial planning
|
||||
- Created comprehensive dm+d medicines data table: standardised strengths, morphine equivalents, Anticholinergic Burden scoring — single source of truth for all medicines analytics
|
||||
- Led DOAC switching financial modelling: interactive dashboard with rebate mechanics, patent expiry timelines
|
||||
- Renegotiated pharmaceutical rebate terms ahead of patent expiry
|
||||
- Tirzepatide commissioning (NICE TA1026): financial projections, cohort identification; authored executive paper advocating primary care model, driving system shift to GP-led delivery
|
||||
- Built Python controlled drug monitoring: oral morphine equivalents across all opioid prescriptions, patient-level tracking, high-risk identification, diversion detection
|
||||
- Improved team data fluency through training and self-serve tools
|
||||
|
||||
### [exp-high-cost-drugs-2022] High-Cost Drugs & Interface Pharmacist
|
||||
NHS Norfolk & Waveney ICB | May 2022–Jul 2024
|
||||
Led NICE TA implementation and high-cost drug pathways across the ICS. Pathways spanning: rheumatology, ophthalmology (wet AMD, DMO, RVO), dermatology, gastroenterology, neurology, migraine.
|
||||
- Blueteq automation: 70% form reduction, 200 hours immediate savings, 7–8 hours ongoing weekly gains
|
||||
- Integrated Blueteq with secondary care databases for accurate high-cost drug spend tracking
|
||||
- Python Sankey chart tool for patient pathway visualisation and trust compliance auditing
|
||||
|
||||
### [exp-pharmacy-manager-2017] Pharmacy Manager
|
||||
Tesco PLC (private sector, NOT NHS) | Nov 2017–May 2022
|
||||
Community pharmacy with full operational autonomy (100-hour contract). LPC representative for Norfolk.
|
||||
- Asthma screening process adopted nationally (~300 branches): reduced pharmacist time 60→6 hours/store/month, ~£1M revenue
|
||||
- Leadership training: Created national induction training plan and eLearning modules for Tesco pharmacy staff
|
||||
- Leadership development: Supervised two staff through NVQ3 to pharmacy technician registration; full HR responsibilities
|
||||
|
||||
## Projects
|
||||
|
||||
### [proj-inv-pharmetrics] PharMetrics Interactive Platform (2024, Live)
|
||||
Real-time medicines expenditure dashboard for NHS decision-makers. Tech: Power BI, SQL, DAX. Tracks £220M prescribing budget.
|
||||
|
||||
### [proj-inv-switching-algorithm] Patient Switching Algorithm (2025, Complete)
|
||||
Python algorithm using GP prescribing data to auto-identify patients for cost-effective alternatives. Tech: Python, Pandas, SQL. 14,000 patients, £2.6M annual savings, novel GP payment system.
|
||||
|
||||
### [proj-inv-blueteq-gen] Blueteq Generator (2023, Complete)
|
||||
Automated Blueteq prior approval form creation. Tech: Python, SQL. 70% form reduction, 200 hours immediate savings, 7–8 hours ongoing weekly gains.
|
||||
|
||||
### [proj-inv-cd-monitoring] CD Monitoring System (2024, Complete)
|
||||
Controlled drug monitoring calculating oral morphine equivalents (OME) across all opioid prescriptions. Tech: Python, SQL. Patient-level tracking, high-risk identification, diversion detection.
|
||||
|
||||
### [proj-inv-sankey-tool] Sankey Chart Analysis Tool (2023, Complete)
|
||||
Patient journey visualisation through high-cost drug pathways. Tech: Python, Matplotlib, SQL. Trust compliance auditing.
|
||||
|
||||
## Education
|
||||
|
||||
### [edu-0] NHS Mary Seacole Programme (2018)
|
||||
NHS Leadership Academy. Score: 78%. Covers change management, healthcare leadership, system-level thinking.
|
||||
|
||||
### [edu-1] MPharm (Hons) 2:1 — University of East Anglia (2011–2015)
|
||||
4-year integrated Master's degree. Research project on drug delivery and cocrystals: 75.1% (Distinction).
|
||||
|
||||
### [edu-2] A-Levels — Highworth Grammar School (2009–2011)
|
||||
Mathematics A*, Chemistry B, Politics C.
|
||||
|
||||
### [edu-3] GPhC Registration — General Pharmaceutical Council (August 2016–Present)
|
||||
Professional registration required to practise as a pharmacist in Great Britain.
|
||||
|
||||
## Skills
|
||||
Technical: [skill-data-analysis] Data Analysis (9yr, 95%), [skill-python] Python (6yr, 90%), [skill-sql] SQL (7yr, 88%), [skill-power-bi] Power BI (5yr, 92%), [skill-javascript-typescript] JavaScript/TypeScript (3yr, 70%), [skill-excel] Excel (9yr, 85%), [skill-algorithm-design] Algorithm Design (3yr, 82%), [skill-data-pipelines] Data Pipelines (2yr, 75%)
|
||||
Domain: [skill-medicines-optimisation] Medicines Optimisation (9yr, 95%), [skill-population-health] Population Health (3yr, 90%), [skill-nice-ta] NICE TA Implementation (3yr, 92%), [skill-health-economics] Health Economics (3yr, 80%), [skill-clinical-pathways] Clinical Pathways (3yr, 88%), [skill-controlled-drugs] Controlled Drugs (1yr, 85%)
|
||||
Leadership: [skill-budget-management] Budget Management (1yr, 90%), [skill-stakeholder-engagement] Stakeholder Engagement (3yr, 88%), [skill-pharma-negotiation] Pharmaceutical Negotiation (1yr, 82%), [skill-team-development] Team Development (8yr, 85%), [skill-change-management] Change Management (7yr, 80%), [skill-financial-modelling] Financial Modelling (1yr, 78%), [skill-executive-comms] Executive Communication (1yr, 85%)
|
||||
|
||||
## Response Rules
|
||||
1. Answer ONLY from the data above. If the answer is not in the data, say "I don't have that information" — never invent facts, roles, dates, achievements, URLs, or contact details.
|
||||
2. Distinguish NHS employment (May 2022–present, ~4 years, all at Norfolk & Waveney ICB) from private sector (Tesco PLC, Nov 2017–May 2022, community pharmacy). Never conflate the two. GPhC registration is a professional licence, not NHS employment.
|
||||
3. When asked broad questions about tools, skills, projects, or achievements across Andy's career, aggregate from ALL roles — do not limit your answer to one position.
|
||||
4. Cite exact numbers, dates, percentages, and outcomes. Never say "approximately" or "around" when exact figures exist in the data.
|
||||
5. For detailed or list-based questions, give a thorough answer covering all relevant items. For simple questions, be concise (2-4 sentences).
|
||||
|
||||
## Item References
|
||||
End your response with a single line listing relevant item IDs from the square-bracketed IDs above:
|
||||
[ITEMS: exp-deputy-head-2024, skill-python]
|
||||
Only include IDs that directly support your answer. Omit the line if none are relevant.`
|
||||
return getLLMCopy().systemPrompt
|
||||
}
|
||||
|
||||
function buildRequestBody(
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { profileContent } from '@/data/profile-content'
|
||||
import type {
|
||||
AchievementCopyEntry,
|
||||
DeepReadonly,
|
||||
EducationCopyEntry,
|
||||
ExperienceEducationUICopy,
|
||||
LatestResultsCopy,
|
||||
LLMCopy,
|
||||
ProfileContent,
|
||||
QuickActionCopyEntry,
|
||||
SidebarCopy,
|
||||
SkillsUICopy,
|
||||
TimelineNarrativeId,
|
||||
TimelineNarrativeEntry,
|
||||
} from '@/types/profile-content'
|
||||
|
||||
export function getProfileContent(): ProfileContent {
|
||||
export function getProfileContent(): DeepReadonly<ProfileContent> {
|
||||
return profileContent
|
||||
}
|
||||
|
||||
@@ -14,14 +22,42 @@ export function getProfileSummaryText(): string {
|
||||
return profileContent.profile.patientSummaryNarrative
|
||||
}
|
||||
|
||||
export function getSidebarCopy(): SidebarCopy {
|
||||
export function getProfileSectionTitle(): string {
|
||||
return profileContent.profile.sectionTitle
|
||||
}
|
||||
|
||||
export function getLatestResultsCopy(): DeepReadonly<LatestResultsCopy> {
|
||||
return profileContent.profile.latestResults
|
||||
}
|
||||
|
||||
export function getSidebarCopy(): DeepReadonly<SidebarCopy> {
|
||||
return profileContent.profile.sidebar
|
||||
}
|
||||
|
||||
export function getExperienceEducationUICopy(): DeepReadonly<ExperienceEducationUICopy> {
|
||||
return profileContent.experienceEducation.ui
|
||||
}
|
||||
|
||||
export function getSkillsUICopy(): DeepReadonly<SkillsUICopy> {
|
||||
return profileContent.skillsNarrative.ui
|
||||
}
|
||||
|
||||
export function getSearchQuickActions(): ReadonlyArray<QuickActionCopyEntry> {
|
||||
return profileContent.searchChat.quickActions
|
||||
}
|
||||
|
||||
export function getLLMCopy(): LLMCopy {
|
||||
export function getAchievementEntries(): ReadonlyArray<AchievementCopyEntry> {
|
||||
return profileContent.resultsNarrative.achievements
|
||||
}
|
||||
|
||||
export function getEducationEntries(): ReadonlyArray<EducationCopyEntry> {
|
||||
return profileContent.experienceEducation.educationEntries
|
||||
}
|
||||
|
||||
export function getLLMCopy(): DeepReadonly<LLMCopy> {
|
||||
return profileContent.searchChat.llm
|
||||
}
|
||||
|
||||
export function getTimelineNarrativeEntry(entityId: TimelineNarrativeId): DeepReadonly<TimelineNarrativeEntry> {
|
||||
return profileContent.timelineNarrative[entityId]
|
||||
}
|
||||
|
||||
+31
-111
@@ -1,10 +1,15 @@
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
import { consultations } from '@/data/consultations'
|
||||
import { documents } from '@/data/documents'
|
||||
import { investigations } from '@/data/investigations'
|
||||
import { skills } from '@/data/skills'
|
||||
import { kpis } from '@/data/kpis'
|
||||
import { timelineConsultations } from '@/data/timeline'
|
||||
import {
|
||||
getAchievementEntries,
|
||||
getEducationEntries,
|
||||
getSearchQuickActions,
|
||||
} from '@/lib/profile-content'
|
||||
import type { DetailPanelContent } from '@/types/pmr'
|
||||
|
||||
export type PaletteSection = 'Experience' | 'Core Skills' | 'Significant Interventions' | 'Achievements' | 'Education' | 'Quick Actions'
|
||||
@@ -34,7 +39,7 @@ export function buildPaletteData(): PaletteItem[] {
|
||||
const items: PaletteItem[] = []
|
||||
|
||||
// Experience — all 4 roles from consultations.ts, open detail panel on select
|
||||
consultations.forEach((c) => {
|
||||
timelineConsultations.forEach((c) => {
|
||||
items.push({
|
||||
id: `exp-${c.id}`,
|
||||
title: c.role,
|
||||
@@ -76,39 +81,12 @@ export function buildPaletteData(): PaletteItem[] {
|
||||
})
|
||||
|
||||
// Achievements — open corresponding KPI detail panel
|
||||
const achievementEntries: Array<{ title: string; sub: string; keywords: string; kpiId: string }> = [
|
||||
{
|
||||
title: '\u00a314.6M Efficiency Savings Identified',
|
||||
sub: 'Data-driven prescribing interventions',
|
||||
keywords: '14.6m efficiency savings identified data-driven prescribing interventions money cost',
|
||||
kpiId: 'savings',
|
||||
},
|
||||
{
|
||||
title: '\u00a3220M Budget Oversight',
|
||||
sub: 'Full analytical accountability to ICB board',
|
||||
keywords: '220m budget oversight analytical accountability icb board',
|
||||
kpiId: 'budget',
|
||||
},
|
||||
{
|
||||
title: 'Power BI Dashboards for 200+ Users',
|
||||
sub: 'Clinicians & commissioners across ICB',
|
||||
keywords: 'power bi dashboards 200 users clinicians commissioners',
|
||||
kpiId: 'years',
|
||||
},
|
||||
{
|
||||
title: '1.2M Population Served',
|
||||
sub: 'Norfolk & Waveney Integrated Care System',
|
||||
keywords: '1.2m population served norfolk waveney ics integrated care system',
|
||||
kpiId: 'population',
|
||||
},
|
||||
]
|
||||
|
||||
achievementEntries.forEach((entry, i) => {
|
||||
getAchievementEntries().forEach((entry, i) => {
|
||||
const kpi = kpis.find(k => k.id === entry.kpiId)
|
||||
items.push({
|
||||
id: `ach-${i}`,
|
||||
title: entry.title,
|
||||
subtitle: entry.sub,
|
||||
subtitle: entry.subtitle,
|
||||
section: 'Achievements',
|
||||
iconVariant: 'amber',
|
||||
iconType: 'achievement',
|
||||
@@ -120,34 +98,11 @@ export function buildPaletteData(): PaletteItem[] {
|
||||
})
|
||||
|
||||
// Education — matching actual entries in EducationSubsection
|
||||
const educationEntries: Array<{ title: string; sub: string; keywords: string }> = [
|
||||
{
|
||||
title: 'NHS Leadership Academy \u2014 Mary Seacole Programme',
|
||||
sub: 'NHS Leadership Academy \u00b7 2018',
|
||||
keywords: 'nhs leadership academy mary seacole programme 2018 qualification management',
|
||||
},
|
||||
{
|
||||
title: 'MPharm (Hons) \u2014 2:1',
|
||||
sub: 'University of East Anglia \u00b7 2011\u20132015',
|
||||
keywords: 'mpharm hons 2:1 university east anglia uea 2011 2015 pharmacy degree',
|
||||
},
|
||||
{
|
||||
title: 'A-Levels',
|
||||
sub: 'Highworth Grammar School \u00b7 2009\u20132011',
|
||||
keywords: 'a-levels mathematics chemistry politics highworth grammar school 2009 2011',
|
||||
},
|
||||
{
|
||||
title: 'GPhC Registration',
|
||||
sub: 'General Pharmaceutical Council \u00b7 August 2016',
|
||||
keywords: 'gphc registration general pharmaceutical council 2016 registered pharmacist',
|
||||
},
|
||||
]
|
||||
|
||||
educationEntries.forEach((entry, i) => {
|
||||
getEducationEntries().forEach((entry, i) => {
|
||||
items.push({
|
||||
id: `edu-${i}`,
|
||||
title: entry.title,
|
||||
subtitle: entry.sub,
|
||||
subtitle: entry.subtitle,
|
||||
section: 'Education',
|
||||
iconVariant: 'purple',
|
||||
iconType: 'edu',
|
||||
@@ -157,43 +112,20 @@ export function buildPaletteData(): PaletteItem[] {
|
||||
})
|
||||
|
||||
// Quick Actions
|
||||
const quickActions: Array<{ title: string; sub: string; keywords: string; action: PaletteAction }> = [
|
||||
{
|
||||
title: 'Download CV',
|
||||
sub: 'Export as PDF',
|
||||
keywords: 'download cv export pdf resume',
|
||||
action: { type: 'download' },
|
||||
},
|
||||
{
|
||||
title: 'Send Email',
|
||||
sub: 'andy@charlwood.xyz',
|
||||
keywords: 'send email contact andy charlwood',
|
||||
action: { type: 'link', url: 'mailto:andy@charlwood.xyz' },
|
||||
},
|
||||
{
|
||||
title: 'View LinkedIn',
|
||||
sub: 'Professional profile',
|
||||
keywords: 'view linkedin professional profile social',
|
||||
action: { type: 'link', url: 'https://linkedin.com/in/andycharlwood' },
|
||||
},
|
||||
{
|
||||
title: 'View Projects',
|
||||
sub: 'GitHub & portfolio',
|
||||
keywords: 'view projects github portfolio code repositories',
|
||||
action: { type: 'link', url: 'https://github.com/andycharlwood' },
|
||||
},
|
||||
]
|
||||
getSearchQuickActions().forEach((entry, i) => {
|
||||
const action: PaletteAction = entry.type === 'download'
|
||||
? { type: 'download' }
|
||||
: { type: 'link', url: entry.url }
|
||||
|
||||
quickActions.forEach((entry, i) => {
|
||||
items.push({
|
||||
id: `action-${i}`,
|
||||
title: entry.title,
|
||||
subtitle: entry.sub,
|
||||
subtitle: entry.subtitle,
|
||||
section: 'Quick Actions',
|
||||
iconVariant: 'teal',
|
||||
iconType: 'action',
|
||||
keywords: entry.keywords,
|
||||
action: entry.action,
|
||||
action,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -248,7 +180,7 @@ export function buildEmbeddingTexts(): Array<{ id: string; text: string }> {
|
||||
const texts: Array<{ id: string; text: string }> = []
|
||||
|
||||
// Consultations (Experience) — enriched with plan outcomes, employer classification, clinical specialties
|
||||
consultations.forEach((c) => {
|
||||
timelineConsultations.forEach((c) => {
|
||||
const isNHS = c.organization.includes('NHS') || c.organization.includes('ICB')
|
||||
const employer = isNHS
|
||||
? `NHS employer: ${c.organization}`
|
||||
@@ -309,14 +241,8 @@ export function buildEmbeddingTexts(): Array<{ id: string; text: string }> {
|
||||
})
|
||||
|
||||
// KPI-backed Achievements — enriched with full story context and outcomes
|
||||
const achievementMap: Array<{ id: string; title: string; subtitle: string; kpiId: string }> = [
|
||||
{ id: 'ach-0', title: '£14.6M Efficiency Savings Identified', subtitle: 'Data-driven prescribing interventions', kpiId: 'savings' },
|
||||
{ id: 'ach-1', title: '£220M Budget Oversight', subtitle: 'Full analytical accountability to ICB board', kpiId: 'budget' },
|
||||
{ id: 'ach-2', title: 'Power BI Dashboards for 200+ Users', subtitle: 'Clinicians & commissioners across ICB', kpiId: 'years' },
|
||||
{ id: 'ach-3', title: '1.2M Population Served', subtitle: 'Norfolk & Waveney Integrated Care System', kpiId: 'population' },
|
||||
]
|
||||
|
||||
achievementMap.forEach((entry) => {
|
||||
getAchievementEntries().forEach((entry, index) => {
|
||||
const id = `ach-${index}`
|
||||
const kpi = kpis.find(k => k.id === entry.kpiId)
|
||||
const explanation = kpi?.explanation ?? ''
|
||||
const storyParts: string[] = []
|
||||
@@ -327,7 +253,7 @@ export function buildEmbeddingTexts(): Array<{ id: string; text: string }> {
|
||||
storyParts.push(`Outcomes: ${kpi.story.outcomes.join('. ')}.`)
|
||||
}
|
||||
texts.push({
|
||||
id: entry.id,
|
||||
id,
|
||||
text: `Achievement: ${entry.title}. ${entry.subtitle}. ${explanation} ${storyParts.join(' ')}`,
|
||||
})
|
||||
})
|
||||
@@ -352,14 +278,15 @@ export function buildEmbeddingTexts(): Array<{ id: string; text: string }> {
|
||||
})
|
||||
|
||||
// Education — enriched with research grades and specific subject details
|
||||
const educationItems: Array<{ id: string; docId: string; fallbackTitle: string; fallbackSub: string }> = [
|
||||
{ id: 'edu-0', docId: 'doc-mary-seacole', fallbackTitle: 'NHS Leadership Academy — Mary Seacole Programme', fallbackSub: 'NHS Leadership Academy · 2018' },
|
||||
{ id: 'edu-1', docId: 'doc-mpharm', fallbackTitle: 'MPharm (Hons) — 2:1', fallbackSub: 'University of East Anglia · 2011–2015' },
|
||||
{ id: 'edu-2', docId: 'doc-alevels', fallbackTitle: 'A-Levels', fallbackSub: 'Highworth Grammar School · 2009–2011' },
|
||||
{ id: 'edu-3', docId: 'doc-gphc', fallbackTitle: 'GPhC Registration', fallbackSub: 'General Pharmaceutical Council · August 2016' },
|
||||
const educationItems: Array<{ id: string; docId: string }> = [
|
||||
{ id: 'edu-0', docId: 'doc-mary-seacole' },
|
||||
{ id: 'edu-1', docId: 'doc-mpharm' },
|
||||
{ id: 'edu-2', docId: 'doc-alevels' },
|
||||
{ id: 'edu-3', docId: 'doc-gphc' },
|
||||
]
|
||||
|
||||
educationItems.forEach((entry) => {
|
||||
educationItems.forEach((entry, index) => {
|
||||
const fallback = getEducationEntries()[index]
|
||||
const doc = documents.find(d => d.id === entry.docId)
|
||||
if (doc) {
|
||||
const research = doc.researchDetail ? ` Research: ${doc.researchDetail}.` : ''
|
||||
@@ -372,22 +299,15 @@ export function buildEmbeddingTexts(): Array<{ id: string; text: string }> {
|
||||
} else {
|
||||
texts.push({
|
||||
id: entry.id,
|
||||
text: `Education: ${entry.fallbackTitle}. ${entry.fallbackSub}.`,
|
||||
text: `Education: ${fallback?.title ?? ''}. ${fallback?.subtitle ?? ''}.`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Quick Actions
|
||||
const quickActionTexts: Array<{ id: string; title: string; subtitle: string }> = [
|
||||
{ id: 'action-0', title: 'Download CV', subtitle: 'Export as PDF' },
|
||||
{ id: 'action-1', title: 'Send Email', subtitle: 'andy@charlwood.xyz' },
|
||||
{ id: 'action-2', title: 'View LinkedIn', subtitle: 'Professional profile' },
|
||||
{ id: 'action-3', title: 'View Projects', subtitle: 'GitHub & portfolio' },
|
||||
]
|
||||
|
||||
quickActionTexts.forEach((entry) => {
|
||||
getSearchQuickActions().forEach((entry, index) => {
|
||||
texts.push({
|
||||
id: entry.id,
|
||||
id: `action-${index}`,
|
||||
text: `${entry.title}. ${entry.subtitle}.`,
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user