diff --git a/web/src/glossary.tsx b/web/src/glossary.tsx new file mode 100644 index 0000000..3cbc5a5 --- /dev/null +++ b/web/src/glossary.tsx @@ -0,0 +1,61 @@ +import { createContext, useContext } from "react"; + +type GlossaryItem = { + term: string; + pageid: string; + contents: string; +}; + +export type Glossary = Map; + +export const GlossaryContext = createContext<{g: Glossary, r: RegExp} | null>(null); + +// A component which wraps arbitrary html in a span, and injects glossary terms +// into it as hoverable pop-up links. The text is immediately rendered normally, +// but after the glossary is loaded (which happens once per page, asynchronously), +// the glossary terms are replaced with elements. + +export const GlossarySpan: React.FC<{content: string}> = ({content}) => { + + const g = useContext(GlossaryContext); + + // If the glossary hasn't loaded yet, just render the text normally. + if (g == null) { + return ; + } + + const glossary = g.g; + const glossaryRegex = g.r; + + // Otherwise, replace glossary terms with links. We can do this in + // O(n * sum of term lengths) by finding String.prototype.indexOf of + // each term in the glossary (since that'd probably be backed by KMP) + // but I think it should be faster to compile a regex state machine + // once and use that instead. + + return { + + const item = glossary.get(match.toLowerCase()); + if (item == undefined) return match; + + const hover_content = item.contents; + const pageid = item.pageid; + + if (pageid == undefined || pageid.trim() == "") { + return ` +
${hover_content}
+ ${match} + `; + } else { + return ` +
${hover_content}
+ + ${match} + + `; + } + + })}} />; +} diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index d280cbd..6e1fa20 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -1,9 +1,50 @@ import { type AppType } from "next/dist/shared/lib/utils"; +import { useEffect, useState } from "react"; import "~/styles/globals.css"; +import { Glossary, GlossaryContext } from "../glossary"; + const MyApp: AppType = ({ Component, pageProps }) => { - return ; + const [glossary, setGlossary] = useState<{ g: Glossary, r: RegExp } | null>(null); + + // fetch glossary and compile regex once on load + useEffect(() => { + if (glossary === null) + tempHackFetch("/questions/glossary") + .then((res) => res.json()) + .then((data) => { + const glossary: Glossary = new Map(Object.entries(data)); + const keys = Array.from(glossary.keys()) + .sort((a, b) => b.length - a.length) // sort by length descending + .map((k) => k.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')) // escape regex chars + .map((k) => `\\b${k}\\b`); // add word boundaries + + const regex = new RegExp(keys.join("|"), "gim"); + setGlossary({ g: glossary, r: regex }); + }); + }, []); + + return ( + + + + ); }; export default MyApp; + +// ------------------- hack until server endpoint is working ------------------- + +const GLOSSARY_JSON = {"chain of thought prompting":{"term":"chain of thought prompting","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"chain-of-thought":{"term":"chain-of-thought","pageid":"8EL7","contents":"

Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.

\n"},"goodhart's law":{"term":"goodhart's law","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"the big g,":{"term":"the big g,","pageid":"8185","contents":"

Goodhart’s law states that when a measure becomes a target, it ceases to be a good measure.

\n"},"terminal goals":{"term":"terminal goals","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"terminal goal":{"term":"terminal goal","pageid":"","contents":"

Goals which are valued as ends in themselves, rather than as instrumental to something else.

\n"},"orthogonality thesis":{"term":"orthogonality thesis","pageid":"6568","contents":"

The thesis that any level of intelligence is compatible with any terminal goals. This implies that intelligence alone is not enough to make a system moral.

\n"},"instrumental convergence":{"term":"instrumental convergence","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"instrumentally convergent goals":{"term":"instrumentally convergent goals","pageid":"897I","contents":"

Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.

\n"},"llm":{"term":"llm","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"large language model":{"term":"large language model","pageid":"","contents":"

A large language model is an AI model which has been trained on a large body of text, in order to produce texts in a human-like way.

\n"},"goal misgeneralization":{"term":"goal misgeneralization","pageid":"","contents":"

pursuing a different goal during deployment from the one that was pursued during training due to distribution shift

\n"},"interpretability":{"term":"interpretability","pageid":"8241","contents":"

Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.

\n"},"existential risk":{"term":"existential risk","pageid":"89LL","contents":"

risks that threaten the destruction of humanity's long-term potential, including human extinction

\n"}} + +const tempHackFetch = (_url: string) => { + return new Promise((resolve, _reject) => { + setTimeout(() => { + resolve({ + ok: true, + json: () => Promise.resolve(GLOSSARY_JSON), + } as unknown as Response); + }, 1000); + }); +} diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index 0e23570..51c8db1 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -10,6 +10,7 @@ import Image from 'next/image'; import Header from "../header"; import { SearchBox, Followup } from "../searchbox"; import logo from "../logo.svg" +import { GlossarySpan } from "../glossary"; type Citation = { title: string; @@ -205,7 +206,7 @@ const ShowAssistantEntry: React.FC<{entry: AssistantEntry}> = ({entry}) => { response.split("\n").map(paragraph => (

{ paragraph.split(in_text_citation_regex).map((text, i) => { if (i % 2 === 0) { - return text.trim(); + return ; } i = parseInt(text) - 1; if (!citations.has(i)) return `[${text}]`; @@ -542,7 +543,7 @@ const Home: NextPage = () => { maxWidth: "99.8%", }} > -