From cf31656b9291488993ad675371ad207872e9d135 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Tue, 20 Feb 2024 11:47:12 +0100 Subject: [PATCH 1/3] Filter out internal tags --- app/routes/questions.$questionId.$.tsx | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/app/routes/questions.$questionId.$.tsx b/app/routes/questions.$questionId.$.tsx index bfb400d6..1177caa1 100644 --- a/app/routes/questions.$questionId.$.tsx +++ b/app/routes/questions.$questionId.$.tsx @@ -8,7 +8,7 @@ import {ArticlesNav} from '~/components/ArticlesNav/Menu' import {fetchGlossary} from '~/routes/questions.glossary' import {loadQuestionDetail, loadTags} from '~/server-utils/stampy' import useToC from '~/hooks/useToC' -import type {Question, Glossary} from '~/server-utils/stampy' +import type {Question, Glossary, Tag} from '~/server-utils/stampy' import {reloadInBackgroundIfNeeded} from '~/server-utils/kv-cache' export const LINK_WITHOUT_DETAILS_CLS = 'link-without-details' @@ -59,11 +59,22 @@ const dummyQuestion = (title: string | undefined) => tags: [], }) as any as Question +const updateTags = (question: Question, tags: Tag[]) => { + const mappedTags = tags.reduce((acc, t) => ({...acc, [t.name]: t}), {}) + return { + ...question, + tags: question.tags + ?.map((name) => mappedTags[name as keyof typeof mappedTags]) + .filter((t?: Tag) => t && !t?.internal) + .map(({name}) => name), + } +} + export default function RenderArticle() { const [glossary, setGlossary] = useState({} as Glossary) const params = useParams() const pageid = params.questionId ?? '😱' - const {data} = useLoaderData() + const {data, tags} = useLoaderData() const {findSection, getArticle, getPath} = useToC() const section = findSection(pageid) @@ -83,8 +94,8 @@ export default function RenderArticle() { key={pageid} fallback={
} > - - {(resolvedValue) => { + + {([resolvedValue, tags]) => { if (resolvedValue instanceof Response) { return } else if (!(resolvedValue as any)?.pageid) { @@ -92,7 +103,12 @@ export default function RenderArticle() { ) } else { - return
+ return ( +
+ ) } }} From a78b656fe0776856b08490f2be37805338de462e Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Tue, 20 Feb 2024 19:54:03 +0100 Subject: [PATCH 2/3] do not show defintion popups in glossary items --- app/components/Article/Contents.tsx | 38 ++++++++++++----------------- app/routesMapper.ts | 2 +- app/server-utils/stampy.ts | 4 ++- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/app/components/Article/Contents.tsx b/app/components/Article/Contents.tsx index 56ffe5f3..2b9c54c6 100644 --- a/app/components/Article/Contents.tsx +++ b/app/components/Article/Contents.tsx @@ -1,4 +1,5 @@ import {useRef, useEffect} from 'react' +import {questionUrl} from '~/routesMapper' import type {Glossary, PageId, GlossaryEntry} from '~/server-utils/stampy' const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string | null => { @@ -56,27 +57,19 @@ const updateTextNodes = (el: Node, textProcessor: (node: Node) => Node) => { * - use each glossary item only once */ const glossaryInjecter = (pageid: string, glossary: Glossary) => { - const unusedGlossaryEntries = Object.values(glossary) - .filter((item) => item.pageid != pageid) - .map(({term}) => term) - .sort((a, b) => b.length - a.length) - .map( - (term) => - [ - new RegExp(`(^|[^\\w-])(${term})($|[^\\w-])`, 'i'), - '$1$2$3', - ] as const - ) - - return (html: string) => { - return unusedGlossaryEntries.reduce((html, [match, replacement], index) => { - if (html.match(match)) { - unusedGlossaryEntries.splice(index, 1) - return html.replace(match, replacement) - } - return html - }, html) - } + const seen = new Set() + return (html: string) => + Object.values(glossary) + .filter((item) => item.pageid != pageid) + .sort((a, b) => b.alias.length - a.alias.length) + .reduce((html, {term, alias}) => { + const match = new RegExp(`(^|[^\\w-])(${alias})($|[^\\w-])`, 'i') + if (!seen.has(term) && html.search(match) >= 0) { + seen.add(term) + return html.replace(match, '$1$2$3') + } + return html + }, html) } const insertGlossary = (pageid: string, glossary: Glossary) => { @@ -101,6 +94,7 @@ const insertGlossary = (pageid: string, glossary: Glossary) => { */ const glossaryEntry = (e: Element) => { const entry = e.textContent && glossary[e?.textContent.toLowerCase().trim()] + console.log(e.textContent, entry) if ( // If the contents of this item aren't simply a glossary item word, then // something has gone wrong and the glossary-entry should be removed @@ -124,7 +118,7 @@ const insertGlossary = (pageid: string, glossary: Glossary) => { if (!entry) return undefined const link = entry.pageid && - `View full definition` + `View full definition` const image = entry.image && `` addPopup( e as HTMLSpanElement, diff --git a/app/routesMapper.ts b/app/routesMapper.ts index 5f65edea..79fe3063 100644 --- a/app/routesMapper.ts +++ b/app/routesMapper.ts @@ -1,5 +1,5 @@ export const questionUrl = ({pageid, title}: {pageid: string; title?: string}) => - `/questions/${pageid}/${title || ''}` + `/questions/${pageid}/${title?.replaceAll(' ', '-') || ''}` export const tagUrl = ({tagId, name}: {tagId?: number | string; name: string}) => tagId ? `/tags/${tagId}/${name}` : `/tags/${name}` diff --git a/app/server-utils/stampy.ts b/app/server-utils/stampy.ts index f2757dcf..75e1b0c8 100644 --- a/app/server-utils/stampy.ts +++ b/app/server-utils/stampy.ts @@ -47,6 +47,7 @@ export type Banner = { } export type GlossaryEntry = { term: string + alias: string pageid: PageId contents: string image: string @@ -328,13 +329,14 @@ export const loadGlossary = withCache('loadGlossary', async () => { const phrases = [values.phrase, ...values.aliases.split('\n')] const item = { pageid, + term: extractText(values.phrase), image: values.image?.url, contents: renderText(pageid, extractText(values.definition)), } return phrases .map((i) => extractText(i)) .filter(Boolean) - .map((phrase) => [phrase.toLowerCase(), {term: phrase, ...item}]) + .map((phrase) => [phrase.toLowerCase(), {alias: phrase, ...item}]) }) .flat() ) From 716e68c1d79f4ad65c9b65d68b9f5797d815eca4 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Tue, 20 Feb 2024 20:41:24 +0100 Subject: [PATCH 3/3] order from backend --- app/routes/questions.toc.ts | 8 ++++++-- app/server-utils/stampy.ts | 3 +++ stories/ArticlesNav.stories.ts | 16 ++++++++++++++++ stories/Grid.stories.tsx | 8 ++++++++ stories/Nav.stories.ts | 16 ++++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/app/routes/questions.toc.ts b/app/routes/questions.toc.ts index 05e4d7c7..2cb8169c 100644 --- a/app/routes/questions.toc.ts +++ b/app/routes/questions.toc.ts @@ -16,6 +16,7 @@ export type TOCItem = { hasText: boolean children?: TOCItem[] category?: Category + order: number } type LoaderResp = { data: TOCItem[] @@ -29,16 +30,19 @@ const getCategory = (tags: string[]): Category => { return undefined } +const byOrder = (a: TOCItem, b: TOCItem) => a.order - b.order const formatQuestion = (level: number) => - ({title, pageid, subtitle, icon, children, text, tags}: Question): TOCItem => ({ + ({title, pageid, subtitle, icon, children, text, tags, order}: Question): TOCItem => ({ title, subtitle: subtitle ? subtitle : undefined, pageid, icon: icon ? icon : undefined, hasText: !!text, - children: level < MAX_LEVELS ? children?.map(formatQuestion(level + 1)) : undefined, + children: + level < MAX_LEVELS ? children?.map(formatQuestion(level + 1)).sort(byOrder) : undefined, category: getCategory(tags), + order: order || 0, }) const getToc = async (request: any) => { diff --git a/app/server-utils/stampy.ts b/app/server-utils/stampy.ts index 75e1b0c8..9729a541 100644 --- a/app/server-utils/stampy.ts +++ b/app/server-utils/stampy.ts @@ -80,6 +80,7 @@ export type Question = { icon?: string parents?: string[] children?: Question[] + order?: number } export type PageId = Question['pageid'] export type NewQuestion = { @@ -133,6 +134,7 @@ export type AnswersRow = CodaRowCommon & { Subtitle?: string Icon?: string Parents?: Entity[] + Order?: number } } type TagsRow = CodaRowCommon & { @@ -292,6 +294,7 @@ const convertToQuestion = ({name, values, updatedAt} = {} as AnswersRow): Questi icon: extractText(values.Icon), parents: !values.Parents ? [] : values.Parents?.map(({name}) => name), updatedAt: updatedAt || values['Doc Last Edited'], + order: values.Order || 0, }) export const loadQuestionDetail = withCache('questionDetail', async (question: string) => { diff --git a/stories/ArticlesNav.stories.ts b/stories/ArticlesNav.stories.ts index 88e2a42a..02896660 100644 --- a/stories/ArticlesNav.stories.ts +++ b/stories/ArticlesNav.stories.ts @@ -18,41 +18,49 @@ const article = { pageid: '9OGZ', icon: '/assets/coded-banner.svg', hasText: true, + order: 0, children: [ { title: 'What would an AGI be able to do?', pageid: 'NH51', hasText: false, + order: 0, }, { title: 'Types of AI', pageid: 'NH50', hasText: false, + order: 0, children: [ { title: 'What are the differences between AGI, transformative AI, and superintelligence?', pageid: '5864', hasText: true, + order: 0, }, { title: 'What is intelligence?', pageid: '6315', hasText: true, + order: 0, }, { title: 'What is artificial general intelligence (AGI)?', pageid: '2374', hasText: true, + order: 0, }, { title: 'What is "superintelligence"?', pageid: '6207', hasText: true, + order: 0, }, { title: 'What is artificial intelligence (AI)?', pageid: '8G1H', hasText: true, + order: 0, }, ], }, @@ -60,16 +68,19 @@ const article = { title: 'Introduction to ML', pageid: 'NH50', hasText: false, + order: 0, children: [ { title: 'What are large language models?', pageid: '8161', hasText: true, + order: 0, }, { title: 'What is compute?', pageid: '9358', hasText: true, + order: 0, }, ], }, @@ -77,26 +88,31 @@ const article = { title: 'Introduction to AI Safety', pageid: 'NH53', hasText: false, + order: 0, children: [ { title: 'Why would an AI do bad things?', pageid: '2400', hasText: true, + order: 0, }, { title: 'How likely is extinction from superintelligent AI?', pageid: '7715', hasText: true, + order: 0, }, { title: 'What is AI safety?', pageid: '8486', hasText: true, + order: 0, }, { title: 'Why is safety important for smarter-than-human AI?', pageid: '6297', hasText: true, + order: 0, }, ], }, diff --git a/stories/Grid.stories.tsx b/stories/Grid.stories.tsx index a2fb8433..9d8631cb 100644 --- a/stories/Grid.stories.tsx +++ b/stories/Grid.stories.tsx @@ -8,6 +8,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section1', pageid: 'https://google.com', hasText: true, + order: 0, }, { title: 'Governance', @@ -15,6 +16,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section2', pageid: 'https://google.com', hasText: true, + order: 0, }, { title: 'Existential risk concepts', @@ -22,6 +24,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section3', pageid: 'https://google.com', hasText: true, + order: 0, }, { title: 'Predictions on advanced AI', @@ -29,6 +32,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section4', pageid: 'https://google.com', hasText: true, + order: 0, }, { title: 'Prominent research organizations', @@ -36,6 +40,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section5', pageid: 'https://google.com', hasText: true, + order: 0, }, { title: '6th item', @@ -43,6 +48,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section6', pageid: 'https://google.com', hasText: true, + order: 10, }, { title: '7th item', @@ -50,6 +56,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section7', pageid: 'https://google.com', hasText: true, + order: 20, }, { title: '8th item', @@ -57,6 +64,7 @@ const toc = [ icon: 'https://cataas.com/cat/says/section8', pageid: 'https://google.com', hasText: true, + order: 30, }, ] diff --git a/stories/Nav.stories.ts b/stories/Nav.stories.ts index 81eb16f3..81bf316e 100644 --- a/stories/Nav.stories.ts +++ b/stories/Nav.stories.ts @@ -81,42 +81,50 @@ export const Primary: Story = { subtitle: 'Basic information about all of this', pageid: '9OGZ', hasText: true, + order: 0, children: [ { title: 'What would an AGI be able to do?', pageid: 'NH50', hasText: false, + order: 0, }, { title: 'Types of AI', pageid: 'NH50', hasText: false, + order: 0, children: [ { title: 'What are the differences between AGI, transformative AI, and superintelligence?', pageid: '5864', hasText: true, + order: 0, }, { title: 'What is intelligence?', pageid: '6315', hasText: true, + order: 12, }, { title: 'What is artificial general intelligence (AGI)?', pageid: '2374', hasText: true, + order: 14, }, { title: 'What is "superintelligence"?', pageid: '6207', hasText: true, + order: 15, }, { title: 'What is artificial intelligence (AI)?', pageid: '8G1H', hasText: true, + order: 17, }, ], }, @@ -124,16 +132,19 @@ export const Primary: Story = { title: 'Introduction to ML', pageid: 'NH50', hasText: false, + order: 13, children: [ { title: 'What are large language models?', pageid: '8161', hasText: true, + order: 1, }, { title: 'What is compute?', pageid: '9358', hasText: true, + order: 15, }, ], }, @@ -141,26 +152,31 @@ export const Primary: Story = { title: 'Introduction to AI Safety', pageid: 'NH50', hasText: false, + order: 1, children: [ { title: 'Why would an AI do bad things?', pageid: '2400', hasText: true, + order: 1, }, { title: 'How likely is extinction from superintelligent AI?', pageid: '7715', hasText: true, + order: 1, }, { title: 'What is AI safety?', pageid: '8486', hasText: true, + order: 1, }, { title: 'Why is safety important for smarter-than-human AI?', pageid: '6297', hasText: true, + order: 51, }, ], },