fix typescript errors

This commit is contained in:
Daniel O'Connell
2023-10-02 14:24:49 +02:00
parent 40b518d8db
commit edb4a89401
16 changed files with 507 additions and 340 deletions
+7 -2
View File
@@ -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",
+19 -16
View File
@@ -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 (
<div className="mt-3 mb-8">
{ entry.content.split("\n").map(paragraph => (
<CitationsBlock
text={paragraph}
citations={entry.citationsMap}
textRenderer={(t) => (<GlossarySpan content={t}/>)}
/>
{entry.content.split("\n").map((paragraph, i) => (
<CitationsBlock
key={i}
text={paragraph}
citations={entry.citationsMap}
textRenderer={(t) => <GlossarySpan content={t} />}
/>
))}
<ul className="mt-5">
{
// show citations
Array.from(entry.citationsMap.values()).map((citation) => (
<li key={citation.index}>
<ShowCitation citation={citation} />
</li>
))
}
<ul className="mt-5">
{ // show citations
Array.from(entry.citationsMap.values()).map(citation => (
<li key={citation.index}>
<ShowCitation citation={citation} />
</li>
))
}
</ul>
</div>
);
+84 -59
View File
@@ -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<string, Citation> = (text, citations) => {
export const findCitations: (
text: string,
citations: Citation[]
) => Map<string, Citation> = (text, citations) => {
// figure out what citations are in the response, and map them appropriately
const cite_map = new Map<string, Citation>();
@@ -53,64 +57,85 @@ export const findCitations: (text: string, citations: Citations[]) => Map<string
let match;
while ((match = regex.exec(text)) !== null) {
const letter = match[1];
const citation = citations[letter.charCodeAt(0) - 'a'.charCodeAt(0)]
if (!cite_map.has(letter!)) {
cite_map.set(letter!, citation);
}
if (!letter || cite_map.has(letter!)) continue;
const citation = citations[letter.charCodeAt(0) - "a".charCodeAt(0)];
if (!citation) continue;
cite_map.set(letter!, citation);
}
return cite_map
}
export const ShowCitation: React.FC<{citation: Citation}> = ({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 (
<A className={Colours[(citation.index - 1) % Colours.length] + " border-2 flex items-center rounded my-2 text-sm no-underline w-fit"}
href={url}>
<A
className={
Colours[(citation.index - 1) % Colours.length] +
" my-2 flex w-fit items-center rounded border-2 text-sm no-underline"
}
href={url}
>
<span className="mx-1"> [{citation.index}] </span>
<p className="mx-1 my-0"> {c_str} </p>
</A>
);
};
export const CitationRef: React.FC<{citation: Citation}> = ({citation}) => {
const url = citation.url && citation.url !== ""
? citation.url
: `https://duckduckgo.com/?q=${encodeURIComponent(citation.title)}`;
return (
<A className={Colours[(citation.index - 1) % Colours.length] + " border-2 rounded text-sm no-underline w-min px-0.5 pb-0.5 ml-1 mr-0.5"}
href={url}>
[{citation.index}]
</A>
);
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 (
<A
className={
Colours[(citation.index - 1) % Colours.length] +
" ml-1 mr-0.5 w-min rounded border-2 px-0.5 pb-0.5 text-sm no-underline"
}
href={url}
>
[{citation.index}]
</A>
);
};
export const CitationsBlock: React.FC<{text: string, citations: Map<string, Citation>, textRenderer: (t: str) => any}> = ({text, citations, textRenderer}) => {
const regex = /\[([a-z]+)\]/g;
return (
<p> {
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 (<CitationRef citation={citations.get(part)} />)
}
})
export const CitationsBlock: React.FC<{
text: string;
citations: Map<string, Citation>;
textRenderer: (t: string) => any;
}> = ({ text, citations, textRenderer }) => {
const regex = /\[([a-z]+)\]/g;
return (
<p>
{" "}
{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 <CitationRef citation={citations.get(part)} key={i} />;
}
</p>
)
}
})}
</p>
);
};
+6 -3
View File
@@ -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 (
<li className="flex mt-1 mb-2">
<TextareaAutosize className="border border-gray-300 px-1 flex-1 resize-none" value={entry.content} />
<li className="mt-1 mb-2 flex">
<TextareaAutosize
className="flex-1 resize-none border border-gray-300 px-1"
value={entry.content}
/>
</li>
);
};
+23 -18
View File
@@ -8,20 +8,21 @@ type GlossaryItem = {
export type Glossary = Map<string, GlossaryItem>;
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 <span dangerouslySetInnerHTML={{__html: content}} />;
return <span dangerouslySetInnerHTML={{ __html: content }} />;
}
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 <span dangerouslySetInnerHTML={{__html: content.replace(glossaryRegex!, (match) => {
return (
<span
dangerouslySetInnerHTML={{
__html: content.replace(glossaryRegex!, (match) => {
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 `
<div class="glossary-hover" nowrap>${hover_content}</div>
<span class="glossary-link">${match}</span>
`;
} else {
return `
} else {
return `
<div class="glossary-hover" nowrap>${hover_content}</div>
<a href="https://aisafety.info/?state=${pageid}"
target="_blank"
@@ -55,7 +58,9 @@ export const GlossarySpan: React.FC<{content: string}> = ({content}) => {
${match}
</a>
`;
}
})}} />;
}
}
}),
}}
/>
);
};
+20 -17
View File
@@ -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" ? (
<span className="flex flex-col font-semibold flex-1 justify-start text-right">
<Link href="/semantic">Show Sources</Link>
</span>
) : (
<span className="flex flex-col font-semibold flex-1 justify-start text-right">
<Link href="/">Go Chat</Link>
</span>
const Header: React.FC<{ page: "index" | "semantic" }> = ({ page }) => {
const sidebar =
page === "index" ? (
<span className="flex flex-1 flex-col justify-start text-right font-semibold">
<Link href="/semantic">Show Sources</Link>
</span>
) : (
<span className="flex flex-1 flex-col justify-start text-right font-semibold">
<Link href="/">Go Chat</Link>
</span>
);
return (
<div className="my-4 flex">
<Image src={logo} alt="aisafety.info logo" width={36} />
<h1 className="my-0 flex-1">AI Safety Chatbot</h1>
{sidebar}
</div>
);
return (<div className="flex my-4">
<Image src={logo} alt="aisafety.info logo" width={36}/>
<h1 className="flex-1 my-0">AI Safety Chatbot</h1>
{sidebar}
</div>);
};
export default Header;
+23 -22
View File
@@ -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 !== "" ? (
<a className={className} href={href} target="_blank" rel="noreferrer">
{children}
</a>
) : (
<a className={className}>
{children}
</a>
);
}
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 !== "" ? (
<a className={className} href={href} target="_blank" rel="noreferrer">
{children}
</a>
) : (
<a className={className}>{children}</a>
);
};
+4 -1
View File
@@ -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 (
<>
<Head>
+71 -58
View File
@@ -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<Followup[]>([]);
const [query, setQuery] = useState(initial_query);
const [loading, setLoading] = useState(false);
const [followups, setFollowups] = useState<Followup[]>([]);
const inputRef = React.useRef<HTMLTextAreaElement>(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 (
<>
<div className="mt-1 flex flex-col items-end">
{" "}
{followups.map((followup, i) => {
return (
<li key={i}>
<button
className="my-1 border border-gray-300 px-1"
onClick={() => {
search(
followup.pageid + "\n" + followup.text,
"followups",
disable,
enable
);
}}
>
<span> {followup.text} </span>
</button>
</li>
);
})}
</div>
<div className="flex flex-col items-end mt-1"> {
followups.map((followup, i) => {
return <li key={i}>
<button className="border border-gray-300 px-1 my-1" onClick={() => {
search(followup.pageid + "\n" + followup.text, "followups", disable, enable);
}}>
<span> {followup.text} </span>
</button>
</li>
})
}</div>
<form className="flex mt-1 mb-2" onSubmit={(e) => {
e.preventDefault();
search(query, "search", disable, enable);
}}>
<TextareaAutosize
className="border border-gray-300 px-1 flex-1 resize-none"
ref={inputRef}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
// if <esc>, blur the input box
if (e.key === "Escape") e.currentTarget.blur();
// if <enter> without <shift>, submit the form (if it's not empty)
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
if (query.trim() !== "") search(query, "search", disable, enable);
}
<form
className="mt-1 mb-2 flex"
onSubmit={(e) => {
e.preventDefault();
search(query, "search", disable, enable);
}}
/>
<button className="ml-2" type="submit" disabled={loading}>
{loading ? "Loading..." : "Search"}
</button>
</form>
</>);
>
<TextareaAutosize
className="flex-1 resize-none border border-gray-300 px-1"
ref={inputRef}
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
// if <esc>, blur the input box
if (e.key === "Escape") e.currentTarget.blur();
// if <enter> without <shift>, submit the form (if it's not empty)
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
if (query.trim() !== "") search(query, "search", disable, enable);
}
}}
/>
<button className="ml-2" type="submit" disabled={loading}>
{loading ? "Loading..." : "Search"}
</button>
</form>
</>
);
};
export const SearchBox = dynamic(() => Promise.resolve(SearchBoxInternal), {
+15 -19
View File
@@ -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<string, Citation>,
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(
/<a(.*?)href="\/\?state=([a-zA-Z0-9]+.*?)"(.*?)<\/a>/g,
(_, pre, linkParts, post) => `<a${pre}href="${STAMPY_URL}/?state=${linkParts}"${post}</a>`
);
(_, pre, linkParts, post) =>
`<a${pre}href="${STAMPY_URL}/?state=${linkParts}"${post}</a>`
);
export const getStampyContent = async (
questionId: string
@@ -196,7 +198,7 @@ export const runSearch = async (
entries: Entry[],
setCurrent: (c: CurrentSearch) => void,
sessionId: string
): SearchResult => {
): Promise<SearchResult> => {
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);
+93 -6
View File
@@ -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":"<p>Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.</p>\n"},"chain-of-thought":{"term":"chain-of-thought","pageid":"8EL7","contents":"<p>Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.</p>\n"},"goodhart's law":{"term":"goodhart's law","pageid":"8185","contents":"<p>Goodharts law states that when a measure becomes a target, it ceases to be a good measure.</p>\n"},"the big g,":{"term":"the big g,","pageid":"8185","contents":"<p>Goodharts law states that when a measure becomes a target, it ceases to be a good measure.</p>\n"},"terminal goals":{"term":"terminal goals","pageid":"","contents":"<p>Goals which are valued as ends in themselves, rather than as instrumental to something else.</p>\n"},"terminal goal":{"term":"terminal goal","pageid":"","contents":"<p>Goals which are valued as ends in themselves, rather than as instrumental to something else.</p>\n"},"orthogonality thesis":{"term":"orthogonality thesis","pageid":"6568","contents":"<p>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.</p>\n"},"instrumental convergence":{"term":"instrumental convergence","pageid":"897I","contents":"<p>Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.</p>\n"},"instrumentally convergent goals":{"term":"instrumentally convergent goals","pageid":"897I","contents":"<p>Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.</p>\n"},"llm":{"term":"llm","pageid":"","contents":"<p>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.</p>\n"},"large language model":{"term":"large language model","pageid":"","contents":"<p>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.</p>\n"},"goal misgeneralization":{"term":"goal misgeneralization","pageid":"","contents":"<p>pursuing a different goal during deployment from the one that was pursued during training due to distribution shift</p>\n"},"interpretability":{"term":"interpretability","pageid":"8241","contents":"<p>Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.</p>\n"},"existential risk":{"term":"existential risk","pageid":"89LL","contents":"<p>risks that threaten the destruction of humanity's long-term potential, including human extinction</p>\n"}}
const GLOSSARY_JSON = {
"chain of thought prompting": {
term: "chain of thought prompting",
pageid: "8EL7",
contents:
"<p>Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.</p>\n",
},
"chain-of-thought": {
term: "chain-of-thought",
pageid: "8EL7",
contents:
"<p>Chain-of-thought prompting is a technique which makes a language model generate intermediate reasoning steps in its output.</p>\n",
},
"goodhart's law": {
term: "goodhart's law",
pageid: "8185",
contents:
"<p>Goodharts law states that when a measure becomes a target, it ceases to be a good measure.</p>\n",
},
"the big g,": {
term: "the big g,",
pageid: "8185",
contents:
"<p>Goodharts law states that when a measure becomes a target, it ceases to be a good measure.</p>\n",
},
"terminal goals": {
term: "terminal goals",
pageid: "",
contents:
"<p>Goals which are valued as ends in themselves, rather than as instrumental to something else.</p>\n",
},
"terminal goal": {
term: "terminal goal",
pageid: "",
contents:
"<p>Goals which are valued as ends in themselves, rather than as instrumental to something else.</p>\n",
},
"orthogonality thesis": {
term: "orthogonality thesis",
pageid: "6568",
contents:
"<p>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.</p>\n",
},
"instrumental convergence": {
term: "instrumental convergence",
pageid: "897I",
contents:
"<p>Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.</p>\n",
},
"instrumentally convergent goals": {
term: "instrumentally convergent goals",
pageid: "897I",
contents:
"<p>Instrumental convergence is the idea that different AI agents, each with distinct terminal goals, will end up adopting many of the same instrumental goals.</p>\n",
},
llm: {
term: "llm",
pageid: "",
contents:
"<p>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.</p>\n",
},
"large language model": {
term: "large language model",
pageid: "",
contents:
"<p>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.</p>\n",
},
"goal misgeneralization": {
term: "goal misgeneralization",
pageid: "",
contents:
"<p>pursuing a different goal during deployment from the one that was pursued during training due to distribution shift</p>\n",
},
interpretability: {
term: "interpretability",
pageid: "8241",
contents:
"<p>Interpretability is an area of alignment research that aims to make machine learning systems easier for humans to understand.</p>\n",
},
"existential risk": {
term: "existential risk",
pageid: "89LL",
contents:
"<p>risks that threaten the destruction of humanity's long-term potential, including human extinction</p>\n",
},
};
const tempHackFetch = (_url: string) => {
return new Promise<Response>((resolve, _reject) => {
@@ -47,4 +134,4 @@ const tempHackFetch = (_url: string) => {
} as unknown as Response);
}, 1000);
});
}
};
+77 -64
View File
@@ -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<Entry[]>([]);
const [current, setCurrent] = useState<CurrentSearch>();
const [sessionId, setSessionId] = useState()
const [citations, setCitations] = useState<Citation>([])
const [sessionId, setSessionId] = useState("");
const [citations, setCitations] = useState<Citation[]>([]);
// [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 = <p>Loading: Waiting for LLM...</p>;
break;
case "streaming":
updateCitations(citations, current)
updateCitations(citations, current);
last_entry = <AssistantEntry entry={current} />;
break;
case "followups":
last_entry = <>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>;
last_entry = (
<>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>
);
break;
}
@@ -159,8 +168,13 @@ const Home: NextPage = () => {
<Page page="index">
<Controls mode={mode} setMode={setMode} />
<h2 className="bg-red-100 text-red-800"><b>WARNING</b>: This is a very <b>early prototype</b>. <Link href="http://bit.ly/stampy-chat-issues" target="_blank">Feedback</Link> welcomed.</h2>
<h2 className="bg-red-100 text-red-800">
<b>WARNING</b>: This is a very <b>early prototype</b>.{" "}
<Link href="http://bit.ly/stampy-chat-issues" target="_blank">
Feedback
</Link>{" "}
welcomed.
</h2>
<ul>
{entries.map((entry, i) => (
@@ -168,8 +182,7 @@ const Home: NextPage = () => {
))}
<SearchBox search={search} />
{ last_entry }
{last_entry}
</ul>
</Page>
);
+25 -22
View File
@@ -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<SemanticEntry[]>([]);
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 (
<div className="my-3">
{/* horizontally split first row, title on left, authors on right */}
<div className="flex">
<h3 className="text-xl flex-1">{entry.title}</h3>
<p className="flex-1 text-right my-0">{entry.authors.join(', ')} - {entry.date}</p>
<h3 className="flex-1 text-xl">{entry.title}</h3>
<p className="my-0 flex-1 text-right">
{entry.authors.join(", ")} - {entry.date}
</p>
</div>
{ entry.text.split("\n").map((paragraph, i) => {
{entry.text.split("\n").map((paragraph, i) => {
const p = paragraph.trim();
if (p === "") return <></>;
if (p === ".....") return <hr key={"b" + i} />;
return <p className="text-sm" key={"p" + i}> {paragraph} </p>
})
}
return (
<p className="text-sm" key={"p" + i}>
{" "}
{paragraph}{" "}
</p>
);
})}
<a href={entry.url}>Read more</a>
</div>
);
};
export default Semantic;
+5 -3
View File
@@ -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`;
+4 -5
View File
@@ -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;
}
+31 -25
View File
@@ -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<string, Citation>;
};
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;