From edb4a894014f1fe63df39d22ccbaa05f5407b703 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Mon, 2 Oct 2023 14:24:49 +0200 Subject: [PATCH] fix typescript errors --- web/package.json | 9 +- web/src/components/assistant.tsx | 35 ++++---- web/src/components/citations.tsx | 143 ++++++++++++++++++------------- web/src/components/entry.tsx | 9 +- web/src/components/glossary.tsx | 41 +++++---- web/src/components/header.tsx | 37 ++++---- web/src/components/html.tsx | 45 +++++----- web/src/components/page.tsx | 5 +- web/src/components/searchbox.tsx | 129 +++++++++++++++------------- web/src/hooks/useSearch.ts | 34 ++++---- web/src/pages/_app.tsx | 99 +++++++++++++++++++-- web/src/pages/index.tsx | 141 ++++++++++++++++-------------- web/src/pages/semantic.tsx | 47 +++++----- web/src/settings.ts | 8 +- web/src/styles/globals.css | 9 +- web/src/types.ts | 56 ++++++------ 16 files changed, 507 insertions(+), 340 deletions(-) diff --git a/web/package.json b/web/package.json index cc45b35..51eafc5 100644 --- a/web/package.json +++ b/web/package.json @@ -5,8 +5,13 @@ "scripts": { "build": "next build", "dev": "next dev", - "lint": "next lint", - "start": "next start" + "start": "next start", + "eslint": "eslint --ignore-pattern .gitignore \"**/*.ts*\"", + "eslint:fix": "eslint --fix --ignore-pattern .gitignore \"**/*.ts*\"", + "prettier": "prettier --check --ignore-path .gitignore \"**/*.{ts*,js,css,md,html}\"", + "prettier:fix": "prettier --write --ignore-path .gitignore \"**/*.{ts*,js,css,md,html}\"", + "lint": "tsc && npm run prettier && npm run eslint", + "lint:fix": "tsc && npm run prettier:fix && npm run eslint:fix" }, "dependencies": { "autosize": "^6.0.1", diff --git a/web/src/components/assistant.tsx b/web/src/components/assistant.tsx index dc292f8..510b12a 100644 --- a/web/src/components/assistant.tsx +++ b/web/src/components/assistant.tsx @@ -1,27 +1,30 @@ import { useState } from "react"; import { ShowCitation, CitationsBlock } from "./citations"; import { GlossarySpan } from "./glossary"; -import type { Citation, AssistantEntry as AssistantType} from "../types"; +import type { Citation, AssistantEntry as AssistantType } from "../types"; -export const AssistantEntry: React.FC<{entry: AssistantType}> = ({entry}) => { +export const AssistantEntry: React.FC<{ entry: AssistantType }> = ({ + entry, +}) => { return (
- { entry.content.split("\n").map(paragraph => ( - ()} - /> + {entry.content.split("\n").map((paragraph, i) => ( + } + /> + ))} +
    + { + // show citations + Array.from(entry.citationsMap.values()).map((citation) => ( +
  • + +
  • )) } -
      - { // show citations - Array.from(entry.citationsMap.values()).map(citation => ( -
    • - -
    • - )) - }
); diff --git a/web/src/components/citations.tsx b/web/src/components/citations.tsx index 8aa44e7..8444e5b 100644 --- a/web/src/components/citations.tsx +++ b/web/src/components/citations.tsx @@ -1,7 +1,6 @@ import type { Citation } from "../types"; import { Colours, A } from "./html"; - export const formatCitations: (text: string) => string = (text) => { // ---------------------- normalize citation form ---------------------- // the general plan here is just to add parsing cases until we can respond @@ -10,39 +9,44 @@ export const formatCitations: (text: string) => string = (text) => { // transform all things that look like [a, b, c] into [a][b][c] let response = text.replace( + /\[((?:[a-z]+,\s*)*[a-z]+)\]/g, // identify groups of this form - /\[((?:[a-z]+,\s*)*[a-z]+)\]/g, // identify groups of this form - - (block: string) => block.split(',') - .map((x) => x.trim()) - .join("][") - ) + (block: string) => + block + .split(",") + .map((x) => x.trim()) + .join("][") + ); // transform all things that look like [(a), (b), (c)] into [(a)][(b)][(c)] response = response.replace( - /\[((?:\([a-z]+\),\s*)*\([a-z]+\))\]/g, // identify groups of this form - (block: string) => block.split(',') - .map((x) => x.trim()) - .join("][") - ) + (block: string) => + block + .split(",") + .map((x) => x.trim()) + .join("][") + ); // transform all things that look like [(a)] into [a] response = response.replace( /\[\(([a-z]+)\)\]/g, (_match: string, x: string) => `[${x}]` - ) + ); // transform all things that look like [ a ] into [a] response = response.replace( /\[\s*([a-z]+)\s*\]/g, (_match: string, x: string) => `[${x}]` - ) + ); return response; -} +}; -export const findCitations: (text: string, citations: Citations[]) => Map = (text, citations) => { +export const findCitations: ( + text: string, + citations: Citation[] +) => Map = (text, citations) => { // figure out what citations are in the response, and map them appropriately const cite_map = new Map(); @@ -53,64 +57,85 @@ export const findCitations: (text: string, citations: Citations[]) => Map = ({citation}) => { + return cite_map; +}; +export const ShowCitation: React.FC<{ citation: Citation }> = ({ + citation, +}) => { var c_str = citation.title; if (citation.authors && citation.authors.length > 0) - c_str += " - " + citation.authors.join(', '); - if (citation.date && citation.date !== "") - c_str += " - " + citation.date; + c_str += " - " + citation.authors.join(", "); + if (citation.date && citation.date !== "") c_str += " - " + citation.date; // if we don't have a url, link to a duckduckgo search for the title instead - const url = citation.url && citation.url !== "" - ? citation.url - : `https://duckduckgo.com/?q=${encodeURIComponent(citation.title)}`; + const url = + citation.url && citation.url !== "" + ? citation.url + : `https://duckduckgo.com/?q=${encodeURIComponent(citation.title)}`; return ( - + [{citation.index}]

{c_str}

); }; -export const CitationRef: React.FC<{citation: Citation}> = ({citation}) => { - const url = citation.url && citation.url !== "" - ? citation.url - : `https://duckduckgo.com/?q=${encodeURIComponent(citation.title)}`; - return ( - - [{citation.index}] - - ); +export const CitationRef: React.FC<{ citation?: Citation }> = ({ + citation, +}) => { + if (!citation) return null; + + const url = + citation.url && citation.url !== "" + ? citation.url + : `https://duckduckgo.com/?q=${encodeURIComponent(citation.title)}`; + return ( + + [{citation.index}] + + ); }; - -export const CitationsBlock: React.FC<{text: string, citations: Map, textRenderer: (t: str) => any}> = ({text, citations, textRenderer}) => { - const regex = /\[([a-z]+)\]/g; - return ( -

{ - text.split(regex).map((part, i) => { - // When splitting, the even parts are basic text sections, while the odd ones are - // citations - if (i % 2 == 0) { - return textRenderer(part) - } else { - return () - } - }) +export const CitationsBlock: React.FC<{ + text: string; + citations: Map; + textRenderer: (t: string) => any; +}> = ({ text, citations, textRenderer }) => { + const regex = /\[([a-z]+)\]/g; + return ( +

+ {" "} + {text.split(regex).map((part, i) => { + // When splitting, the even parts are basic text sections, while the odd ones are + // citations + if (i % 2 == 0) { + return textRenderer(part); + } else { + return ; } -

- ) -} + })} +

+ ); +}; diff --git a/web/src/components/entry.tsx b/web/src/components/entry.tsx index d70afdc..2e052b6 100644 --- a/web/src/components/entry.tsx +++ b/web/src/components/entry.tsx @@ -9,12 +9,15 @@ import { AssistantEntry } from "./assistant"; import { GlossarySpan } from "./glossary"; import Image from "next/image"; import logo from "../logo.svg"; -import TextareaAutosize from 'react-textarea-autosize'; +import TextareaAutosize from "react-textarea-autosize"; export const User = ({ entry }: { entry: UserEntry }) => { return ( -
  • - +
  • +
  • ); }; diff --git a/web/src/components/glossary.tsx b/web/src/components/glossary.tsx index 0748a49..8d4994f 100644 --- a/web/src/components/glossary.tsx +++ b/web/src/components/glossary.tsx @@ -8,20 +8,21 @@ type GlossaryItem = { export type Glossary = Map; -export const GlossaryContext = createContext<{g: Glossary, r: RegExp} | null>(null); +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}) => { - +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 ; + return ; } const glossary = g.g; @@ -33,21 +34,23 @@ export const GlossarySpan: React.FC<{content: string}> = ({content}) => { // but I think it should be faster to compile a regex state machine // once and use that instead. - return { + return ( + { + const item = glossary.get(match.toLowerCase()); + if (item == undefined) return match; - const item = glossary.get(match.toLowerCase()); - if (item == undefined) return match; + const hover_content = item.contents; + const pageid = item.pageid; - const hover_content = item.contents; - const pageid = item.pageid; - - if (pageid == undefined || pageid.trim() == "") { - return ` + if (pageid == undefined || pageid.trim() == "") { + return `
    ${hover_content}
    ${match} `; - } else { - return ` + } else { + return `
    ${hover_content}
    = ({content}) => { ${match} `; - } - - })}} />; -} + } + }), + }} + /> + ); +}; diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx index 79c6196..9f10fc7 100644 --- a/web/src/components/header.tsx +++ b/web/src/components/header.tsx @@ -1,24 +1,27 @@ import React from "react"; import Link from "next/link"; -import Image from 'next/image'; -import logo from "../logo.svg" +import Image from "next/image"; +import logo from "../logo.svg"; -const Header: React.FC<{page: "index" | "semantic"}> = ({page}) => { - const sidebar = page === "index" ? ( - - Show Sources - - ) : ( - - Go Chat - +const Header: React.FC<{ page: "index" | "semantic" }> = ({ page }) => { + const sidebar = + page === "index" ? ( + + Show Sources + + ) : ( + + Go Chat + + ); + + return ( +
    + aisafety.info logo +

    AI Safety Chatbot

    + {sidebar} +
    ); - - return (
    - aisafety.info logo -

    AI Safety Chatbot

    - {sidebar} -
    ); }; export default Header; diff --git a/web/src/components/html.tsx b/web/src/components/html.tsx index 4bc5b01..8312bb7 100644 --- a/web/src/components/html.tsx +++ b/web/src/components/html.tsx @@ -5,27 +5,28 @@ // the source file for it to be included in the build export const Colours = [ - "bg-red-100 border-red-300 text-red-800", - "bg-amber-100 border-amber-300 text-amber-800", - "bg-orange-100 border-orange-300 text-orange-800", - "bg-lime-100 border-lime-300 text-lime-800", - "bg-green-100 border-green-300 text-green-800", - "bg-cyan-100 border-cyan-300 text-cyan-800", - "bg-blue-100 border-blue-300 text-blue-800", - "bg-violet-100 border-violet-300 text-violet-800", - "bg-pink-100 border-pink-300 text-pink-800", + "bg-red-100 border-red-300 text-red-800", + "bg-amber-100 border-amber-300 text-amber-800", + "bg-orange-100 border-orange-300 text-orange-800", + "bg-lime-100 border-lime-300 text-lime-800", + "bg-green-100 border-green-300 text-green-800", + "bg-cyan-100 border-cyan-300 text-cyan-800", + "bg-blue-100 border-blue-300 text-blue-800", + "bg-violet-100 border-violet-300 text-violet-800", + "bg-pink-100 border-pink-300 text-pink-800", ]; - -export const A: React.FC<{href: string, className?: string, children: React.ReactNode}> = ({href, className, children}) => { - // link element that only populates the href field if the contents are there - return href && href !== "" ? ( - - {children} - - ) : ( - - {children} - - ); -} +export const A: React.FC<{ + href: string; + className?: string; + children: React.ReactNode; +}> = ({ href, className, children }) => { + // link element that only populates the href field if the contents are there + return href && href !== "" ? ( + + {children} + + ) : ( + {children} + ); +}; diff --git a/web/src/components/page.tsx b/web/src/components/page.tsx index 1969234..f64337b 100644 --- a/web/src/components/page.tsx +++ b/web/src/components/page.tsx @@ -2,7 +2,10 @@ import React, { ReactNode } from "react"; import Head from "next/head"; import Header from "./header"; -const Page: React.FC<{children: ReactNode, page: "index" | "semantic"}> = ({page, children}) => { +const Page: React.FC<{ children: ReactNode; page: "index" | "semantic" }> = ({ + page, + children, +}) => { return ( <> diff --git a/web/src/components/searchbox.tsx b/web/src/components/searchbox.tsx index 4b3c433..ed1733b 100644 --- a/web/src/components/searchbox.tsx +++ b/web/src/components/searchbox.tsx @@ -1,9 +1,8 @@ import React from "react"; import { useState, useEffect } from "react"; import type { Followup } from "../types"; -import TextareaAutosize from 'react-textarea-autosize'; -import dynamic from 'next/dynamic' -import type { Followup } from "../types"; +import TextareaAutosize from "react-textarea-autosize"; +import dynamic from "next/dynamic"; // initial questions to fill the search box with. export const initialQuestions: string[] = [ @@ -13,36 +12,36 @@ export const initialQuestions: string[] = [ "How could an AI possibly be an x-risk when some populations aren't even connected to the internet?", "I'm not convinced, why is this important?", "Summarize the differences in opinion between Eliezer Yudkowsky and Paul Christiano.", - "What are \"RAAPs\"?", - "What are \"scaling laws\" and how are they relevant to safety?", + 'What are "RAAPs"?', + 'What are "scaling laws" and how are they relevant to safety?', "What are some of the different research approaches?", "What are the differences between Inner and Outer alignment?", - "What does the term \"x-risk\" mean?", - "What is \"FOOM\"?", - "What is \"instrumental convergence\"?", + 'What does the term "x-risk" mean?', + 'What is "FOOM"?', + 'What is "instrumental convergence"?', "What is a hard takeoff?", "What is a mesa-optimizer?", "What is AI safety and alignment?", "What is an AI arms race?", "What is an Intelligence Explosion?", - "What is the \"orthogonality thesis\"?", - "Why would we expect AI to be \"misaligned by default\"?", -] + 'What is the "orthogonality thesis"?', + 'Why would we expect AI to be "misaligned by default"?', +]; - -const SearchBoxInternal: React.FC<{search: ( +const SearchBoxInternal: React.FC<{ + search: ( query: string, query_source: "search" | "followups", disable: () => void, - enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void, - ) => void, -}> = ({search}) => { + enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void + ) => void; +}> = ({ search }) => { + const initial_query = + initialQuestions[Math.floor(Math.random() * initialQuestions.length)] || ""; - const initial_query = initialQuestions[Math.floor(Math.random() * initialQuestions.length)] || ""; - - const [ query, setQuery ] = useState(initial_query); - const [ loading, setLoading ] = useState(false); - const [ followups, setFollowups ] = useState([]); + const [query, setQuery] = useState(initial_query); + const [loading, setLoading] = useState(false); + const [followups, setFollowups] = useState([]); const inputRef = React.useRef(null); @@ -58,7 +57,6 @@ const SearchBoxInternal: React.FC<{search: ( setQuery(""); }; - useEffect(() => { // set focus on the input box if (!loading) inputRef.current?.focus(); @@ -73,44 +71,59 @@ const SearchBoxInternal: React.FC<{search: ( }, []); if (loading) return <>; - return (<> + return ( + <> +
    + {" "} + {followups.map((followup, i) => { + return ( +
  • + +
  • + ); + })} +
    -
    { - followups.map((followup, i) => { - return
  • - -
  • - }) - }
    - -
    { - e.preventDefault(); - search(query, "search", disable, enable); - }}> - setQuery(e.target.value)} - onKeyDown={(e) => { - // if , blur the input box - if (e.key === "Escape") e.currentTarget.blur(); - // if without , submit the form (if it's not empty) - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - if (query.trim() !== "") search(query, "search", disable, enable); - } + { + e.preventDefault(); + search(query, "search", disable, enable); }} - /> - - - ); + > + setQuery(e.target.value)} + onKeyDown={(e) => { + // if , blur the input box + if (e.key === "Escape") e.currentTarget.blur(); + // if without , submit the form (if it's not empty) + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + if (query.trim() !== "") search(query, "search", disable, enable); + } + }} + /> + + + + ); }; export const SearchBox = dynamic(() => Promise.resolve(SearchBoxInternal), { diff --git a/web/src/hooks/useSearch.ts b/web/src/hooks/useSearch.ts index d614ae3..776194f 100644 --- a/web/src/hooks/useSearch.ts +++ b/web/src/hooks/useSearch.ts @@ -5,15 +5,15 @@ import type { AssistantEntry, ErrorMessage, StampyMessage, - CurrentSearch, Followup, + CurrentSearch, SearchResult, } from "../types"; -import { formatCitations, findCitations } from '../components/citations'; +import { formatCitations, findCitations } from "../components/citations"; const MAX_FOLLOWUPS = 4; -const DATA_HEADER = "data: " -const EVENT_END_HEADER = "event: close" +const DATA_HEADER = "data: "; +const EVENT_END_HEADER = "event: close"; type HistoryEntry = { role: "error" | "stampy" | "assistant" | "user"; @@ -57,7 +57,7 @@ export const extractAnswer = async ( role: "assistant", content: "", citations: [], - citationsMap: Map, + citationsMap: new Map(), }; var followups: Followup[] = []; for await (var data of iterateData(res)) { @@ -87,9 +87,9 @@ export const extractAnswer = async ( break; case "followups": - // add any potential followup questions - followups = data.followups.map((value) => value as Followup); - break; + // add any potential followup questions + followups = data.followups.map((value: any) => value as Followup); + break; case "done": break; case "error": @@ -140,10 +140,12 @@ export const queryLLM = async ( } }; -const cleanStampyContent = (contents: string) => contents.replace( +const cleanStampyContent = (contents: string) => + contents.replace( //g, - (_, pre, linkParts, post) => `` -); + (_, pre, linkParts, post) => + `` + ); export const getStampyContent = async ( questionId: string @@ -196,7 +198,7 @@ export const runSearch = async ( entries: Entry[], setCurrent: (c: CurrentSearch) => void, sessionId: string -): SearchResult => { +): Promise => { if (query_source === "search") { const history = entries .filter((entry) => entry.role !== "error") @@ -205,13 +207,7 @@ export const runSearch = async ( content: entry.content.trim(), })); - return await queryLLM( - query, - mode, - history, - setCurrent, - sessionId - ); + return await queryLLM(query, mode, history, setCurrent, sessionId); } else { // ----------------- HUMAN AUTHORED CONTENT RETRIEVAL ------------------ const [questionId] = query.split("\n", 2); diff --git a/web/src/pages/_app.tsx b/web/src/pages/_app.tsx index 792401c..c03e5ad 100644 --- a/web/src/pages/_app.tsx +++ b/web/src/pages/_app.tsx @@ -6,7 +6,9 @@ import "~/styles/globals.css"; import { Glossary, GlossaryContext } from "../components/glossary"; const MyApp: AppType = ({ Component, pageProps }) => { - const [glossary, setGlossary] = useState<{ g: Glossary, r: RegExp } | null>(null); + const [glossary, setGlossary] = useState<{ g: Glossary; r: RegExp } | null>( + null + ); // fetch glossary and compile regex once on load useEffect(() => { @@ -16,9 +18,9 @@ const MyApp: AppType = ({ Component, pageProps }) => { .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 + .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 }); @@ -36,7 +38,92 @@ 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 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) => { @@ -47,4 +134,4 @@ const tempHackFetch = (_url: string) => { } as unknown as Response); }, 1000); }); -} +}; diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index 33c46f9..aa584f4 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -1,21 +1,22 @@ import { type NextPage } from "next"; import { useState, useEffect } from "react"; import Link from "next/link"; -import Image from 'next/image'; +import Image from "next/image"; -import Page from "../components/page" -import { API_URL } from "../settings" +import Page from "../components/page"; +import { API_URL } from "../settings"; import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch"; import type { - CurrentSearch, - Citation, - Entry, - UserEntry, - AssistantEntry as AssistantEntryType, - ErrorMessage, - StampyMessage + CurrentSearch, + Citation, + Entry, + UserEntry, + AssistantEntry as AssistantEntryType, + ErrorMessage, + StampyMessage, + Followup, } from "../types"; -import { SearchBox, Followup } from "../components/searchbox"; +import { SearchBox } from "../components/searchbox"; import { GlossarySpan } from "../components/glossary"; import { Controls, Mode } from "../components/controls"; import { AssistantEntry } from "../components/assistant"; @@ -23,36 +24,36 @@ import { Entry as EntryTag } from "../components/entry"; const MAX_FOLLOWUPS = 4; -type State = { - state: "idle"; -} | { - state: "loading"; - phase: "semantic" | "prompt" | "llm"; - citations: Citation[]; -} | { - state: "streaming"; - response: AssistantEntryType; -}; - -type Mode = "rookie" | "concise" | "default"; - +type State = + | { + state: "idle"; + } + | { + state: "loading"; + phase: "semantic" | "prompt" | "llm"; + citations: Citation[]; + } + | { + state: "streaming"; + response: AssistantEntryType; + }; // smooth-scroll to the bottom of the window if we're already less than 30% a screen away // note: finicky interaction with "smooth" - maybe fix later. function scroll30() { - if ( - document.documentElement.scrollHeight - window.scrollY > - window.innerHeight * 1.3 - ) - return; - window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); + if ( + document.documentElement.scrollHeight - window.scrollY > + window.innerHeight * 1.3 + ) + return; + window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); } const Home: NextPage = () => { const [entries, setEntries] = useState([]); const [current, setCurrent] = useState(); - const [sessionId, setSessionId] = useState() - const [citations, setCitations] = useState([]) + const [sessionId, setSessionId] = useState(""); + const [citations, setCitations] = useState([]); // [state, ready to save to localstorage] const [mode, setMode] = useState<[Mode, boolean]>(["default", false]); @@ -60,12 +61,11 @@ const Home: NextPage = () => { // store mode in localstorage useEffect(() => { if (mode[1]) localStorage.setItem("chat_mode", mode[0]); - }, [mode]); // initial load useEffect(() => { - const mode = localStorage.getItem("chat_mode") as Mode || "default"; + const mode = (localStorage.getItem("chat_mode") as Mode) || "default"; setMode([mode, true]); setSessionId(crypto.randomUUID()); }, []); @@ -77,38 +77,45 @@ const Home: NextPage = () => { } }; - const updateCitations = (allCitations: Citation[], current: CurrentSearch) => { - const entryCitations = Array.from(current.citationsMap.values()); - if (!entryCitations.some(c => !c.index)) { + const updateCitations = ( + allCitations: Citation[], + current: CurrentSearch + ) => { + if (!current) return; + + const entryCitations = Array.from( + current.citationsMap.values() + ) as Citation[]; + if (!entryCitations.some((c) => !c.index)) { // All of the entries citations have indexes, so there weren't any changes since the last check - return + return; } // Get a mapping of all known citations, so as to reuse them if they appear again - const citationsMapping = Object.fromEntries(allCitations.map(c => ([c.title + c.url, c.index]))); + const citationsMapping = Object.fromEntries( + allCitations.map((c) => [c.title + c.url, c.index]) + ); - entryCitations.forEach( - (c) => { - const hash = c.title + c.url; - if (!citationsMapping[hash]) { - c.index = allCitations.length + 1; - allCitations.push(c); - } else { - c.index = citationsMapping[hash]; - } + entryCitations.forEach((c) => { + const hash = c.title + c.url; + const index = citationsMapping[hash]; + if (index !== undefined) { + c.index = index; + } else { + c.index = allCitations.length + 1; + allCitations.push(c); } - ) - setCitations(allCitations) - setCurrent(current) - } + }); + setCitations(allCitations); + setCurrent(current); + }; const search = async ( query: string, query_source: "search" | "followups", disable: () => void, - enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void, + enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void ) => { - // clear the query box, append to entries const userEntry: Entry = { role: "user", @@ -123,7 +130,7 @@ const Home: NextPage = () => { mode[0], entries, updateCurrent, - sessionId, + sessionId ); setCurrent(undefined); @@ -144,14 +151,16 @@ const Home: NextPage = () => { last_entry =

    Loading: Waiting for LLM...

    ; break; case "streaming": - updateCitations(citations, current) + updateCitations(citations, current); last_entry = ; break; case "followups": - last_entry = <> - -

    Checking for followups...

    - ; + last_entry = ( + <> + +

    Checking for followups...

    + + ); break; } @@ -159,8 +168,13 @@ const Home: NextPage = () => { -

    WARNING: This is a very early prototype. Feedback welcomed.

    - +

    + WARNING: This is a very early prototype.{" "} + + Feedback + {" "} + welcomed. +

      {entries.map((entry, i) => ( @@ -168,8 +182,7 @@ const Home: NextPage = () => { ))} - { last_entry } - + {last_entry}
    ); diff --git a/web/src/pages/semantic.tsx b/web/src/pages/semantic.tsx index f87bdc0..e50c07c 100644 --- a/web/src/pages/semantic.tsx +++ b/web/src/pages/semantic.tsx @@ -1,28 +1,29 @@ import { type NextPage } from "next"; -import React from "react"; -import Page from "../components/page" -import { SearchBox, Followup } from "../components/searchbox"; -import { useState } from "react"; -import { API_URL } from "../settings" +import React, { useState } from "react"; +import { API_URL } from "../settings"; +import type { Followup } from "../types"; +import Page from "../components/page"; +import { SearchBox } from "../components/searchbox"; const Semantic: NextPage = () => { - const [results, setResults] = useState([]); const semantic_search = async ( query: string, _query_source: "search" | "followups", disable: () => void, - enable: (f_set: Followup[]) => void, + enable: (f_set: Followup[]) => void ) => { - disable(); const res = await fetch(API_URL + "/semantic", { method: "POST", - headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, - body: JSON.stringify({query: query}), - }) + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + body: JSON.stringify({ query: query }), + }); if (!res.ok) { enable([]); @@ -33,7 +34,6 @@ const Semantic: NextPage = () => { setResults(data); enable([]); - }; return ( @@ -65,28 +65,31 @@ type SemanticEntry = { text: string; }; -const ShowSemanticEntry: React.FC<{entry: SemanticEntry}> = ({entry}) => { - +const ShowSemanticEntry: React.FC<{ entry: SemanticEntry }> = ({ entry }) => { return (
    - {/* horizontally split first row, title on left, authors on right */}
    -

    {entry.title}

    -

    {entry.authors.join(', ')} - {entry.date}

    +

    {entry.title}

    +

    + {entry.authors.join(", ")} - {entry.date} +

    - { entry.text.split("\n").map((paragraph, i) => { + {entry.text.split("\n").map((paragraph, i) => { const p = paragraph.trim(); if (p === "") return <>; if (p === ".....") return
    ; - return

    {paragraph}

    - }) - } + return ( +

    + {" "} + {paragraph}{" "} +

    + ); + })} Read more
    ); }; - export default Semantic; diff --git a/web/src/settings.ts b/web/src/settings.ts index abb4211..5dd7442 100644 --- a/web/src/settings.ts +++ b/web/src/settings.ts @@ -1,3 +1,5 @@ -export const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:3001"; -export const STAMPY_URL = process.env.STAMPY_URL || "https://aisafety.info" -export const STAMPY_CONTENT_URL = process.env.STAMPY_CONTENT_URL || `${API_URL}/human` +export const API_URL = + process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:3001"; +export const STAMPY_URL = process.env.STAMPY_URL || "https://aisafety.info"; +export const STAMPY_CONTENT_URL = + process.env.STAMPY_CONTENT_URL || `${API_URL}/human`; diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index c9c3612..314f607 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -3,11 +3,11 @@ @tailwind utilities; h1 { - @apply text-4xl font-bold my-4; + @apply my-4 text-4xl font-bold; } h2 { - @apply text-xl font-semibold my-4; + @apply my-4 text-xl font-semibold; } main { @@ -28,7 +28,7 @@ p { } button { - @apply bg-white hover:bg-gray-300 px-0.5 py-0 w-fit h-fit; + @apply h-fit w-fit bg-white px-0.5 py-0 hover:bg-gray-300; @apply border border-gray-300; @apply text-gray-700; } @@ -37,14 +37,13 @@ ol { @apply list-decimal; } - /* glossary terms with a definition that shows on hover */ .glossary-hover { position: absolute; display: none; width: 300px; transform: translateY(1.7rem); - @apply bg-white hover:bg-gray-300 h-fit px-3; + @apply h-fit bg-white px-3 hover:bg-gray-300; @apply border border-gray-300; @apply text-black; } diff --git a/web/src/types.ts b/web/src/types.ts index 5cafe64..b7fe0c4 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -1,38 +1,44 @@ export type Citation = { - title: string; - authors: string[]; - date: string; - url: string; - index: number; -} + title: string; + authors: string[]; + date: string; + url: string; + index: number; +}; export type Followup = { - text: string; - pageid: string; - score: number; -} + text: string; + pageid: string; + score: number; +}; export type Entry = UserEntry | AssistantEntry | ErrorMessage | StampyMessage; export type UserEntry = { - role: "user"; - content: string; -} + role: "user"; + content: string; +}; export type AssistantEntry = { - role: "assistant"; - content: string; - citations: Citation[]; - base_count: number; // the number to start counting citations at -} + role: "assistant"; + content: string; + citations: Citation[]; + citationsMap: Map; +}; export type ErrorMessage = { - role: "error"; - content: string; -} + role: "error"; + content: string; +}; export type StampyMessage = { - role: "stampy"; - content: string; - url: string; -} + role: "stampy"; + content: string; + url: string; +}; + +export type SearchResult = { + followups?: Followup[] | ((f: Followup[]) => Followup[]); + result: Entry; +}; +export type CurrentSearch = (AssistantEntry & { phase?: string }) | undefined;