Hooks for settings and citations

This commit is contained in:
Daniel O'Connell
2023-10-19 23:01:07 +02:00
parent ed7a5d46a1
commit 52d7fa81d3
13 changed files with 365 additions and 350 deletions
+43 -65
View File
@@ -9,6 +9,7 @@ import type {
LLMSettings,
Followup,
} from "../types";
import useCitations from "../hooks/useCitations";
import { SearchBox } from "../components/searchbox";
import { AssistantEntry } from "../components/assistant";
import { Entry as EntryTag } from "../components/entry";
@@ -40,6 +41,38 @@ function scroll30() {
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
}
export const ChatResponse = ({
current,
defaultElem,
}: {
current: CurrentSearch;
defaultElem?: any;
}) => {
switch (current?.phase) {
case "started":
return <p>Loading: Sending query...</p>;
case "semantic":
return <p>Loading: Performing semantic search...</p>;
case "context":
return <p>Loading: Creating context...</p>;
case "prompt":
return <p>Loading: Creating prompt...</p>;
case "llm":
return <p>Loading: Waiting for LLM...</p>;
case "streaming":
return <AssistantEntry entry={current} />;
case "followups":
return (
<>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>
);
default:
return defaultElem;
}
};
type ChatParams = {
sessionId: string;
settings: LLMSettings;
@@ -50,46 +83,17 @@ type ChatParams = {
const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
const [entries, setEntries] = useState<Entry[]>([]);
const [current, setCurrent] = useState<CurrentSearch>();
const [citations, setCitations] = useState<Citation[]>([]);
const { citations, setEntryCitations } = useCitations();
const updateCurrent = (current: CurrentSearch) => {
setCurrent(current);
if (current?.phase === "streaming") {
setCurrent(setEntryCitations(current));
scroll30();
} else {
setCurrent(current);
}
};
const updateCitations = (
allCitations: Citation[],
current?: CurrentSearch
) => {
if (!current) return;
const entryCitations = Array.from(current.citationsMap.values());
if (!entryCitations.some((c) => !c.index)) {
// All of the entries citations have indexes, so there weren't any changes since the last check
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])
);
entryCitations.forEach((c) => {
const hash = c.title + c.url;
const index = citationsMapping[hash];
if (!index) {
c.index = allCitations.length + 1;
allCitations.push(c);
} else {
c.index = index;
}
});
setCitations(allCitations);
setCurrent(current);
};
const addEntry = (entry: Entry) => {
setEntries((prev) => {
const entries = [...prev, entry];
@@ -132,36 +136,6 @@ const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
setCurrent(undefined);
};
var last_entry = <></>;
switch (current?.phase) {
case "semantic":
last_entry = <p>Loading: Performing semantic search...</p>;
break;
case "prompt":
last_entry = <p>Loading: Creating prompt...</p>;
break;
case "llm":
last_entry = <p>Loading: Waiting for LLM...</p>;
break;
case "streaming":
updateCitations(citations, current);
last_entry = <AssistantEntry entry={current} />;
break;
case "followups":
last_entry = (
<>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>
);
break;
default:
last_entry = (
<button onClick={() => setEntries([])}>Clear history</button>
);
break;
}
return (
<ul className="flex-auto">
{entries.map(
@@ -185,8 +159,12 @@ const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
)
)}
<SearchBox search={search} onQuery={onQuery} />
{last_entry}
<ChatResponse
current={current}
defaultElem={
<button onClick={() => setEntries([])}>Clear history</button>
}
/>
</ul>
);
};
+28 -46
View File
@@ -1,57 +1,39 @@
import type { Mode } from "../types";
export const Controls = ({
mode,
setMode,
}: {
mode: [Mode, boolean];
const MODES = {
rookie:
"For people who are new to the field of AI alignment. The " +
"answer might be longer, since technical terms will be " +
"explained in more detail and less background will be assumed.",
concise:
"Quick and to the point. Followup questions may need to be " +
"asked to get the full picture of what's going on.",
default: "A balanced default mode.",
};
type ControlsType = {
mode: Mode;
setMode: (m: any) => void;
}) => {
};
export const Controls = ({ mode, setMode }: ControlsType) => {
{
/* three buttons for the three modes, place far right, 1rem between each */
}
return (
<div className="ml-auto mr-0 mb-5 flex w-fit flex-row justify-center gap-2">
<button
className={
"border border-gray-300 px-1 " +
(mode[1] && mode[0] === "rookie" ? "bg-gray-200" : "")
}
onClick={() => {
setMode(["rookie", true]);
}}
title="For people who are new to the field of AI alignment. The
answer might be longer, since technical terms will be
explained in more detail and less background will be
assumed."
>
rookie
</button>
<button
className={
"border border-gray-300 px-1 " +
(mode[1] && mode[0] === "concise" ? "bg-gray-200" : "")
}
onClick={() => {
setMode(["concise", true]);
}}
title="Quick and to the point. Followup questions may need to be
asked to get the full picture of what's going on."
>
concise
</button>
<button
className={
"border border-gray-300 px-1 " +
(mode[1] && mode[0] === "default" ? "bg-gray-200" : "")
}
onClick={() => {
setMode(["default", true]);
}}
title="A balanced default mode."
>
default
</button>
{Object.entries(MODES).map(([modeType, title]) => (
<button
className={
"border border-gray-300 px-1 " +
(mode === modeType ? "bg-gray-200" : "")
}
onClick={() => setMode(modeType)}
title={title}
key={modeType}
>
{modeType}
</button>
))}
</div>
);
};
+3 -3
View File
@@ -3,9 +3,9 @@ import Link from "next/link";
import Image from "next/image";
import logo from "../logo.svg";
const Header: React.FC<{ page: "index" | "semantic" | "playground" }> = ({
page,
}) => {
export type Page = "index" | "semantic" | "playground" | "tester";
const Header: React.FC<{ page: Page }> = ({ page }) => {
const sidebar =
page === "index" ? (
<span className="flex flex-1 flex-col justify-start text-right font-semibold">
+7 -5
View File
@@ -1,17 +1,19 @@
import React, { ReactNode } from "react";
import Head from "next/head";
import type { Page as PageType } from "./header";
import Header from "./header";
const Page: React.FC<{ children: ReactNode; page: "index" | "semantic" }> = ({
page,
children,
}) => {
const Page: React.FC<{
children: ReactNode;
widescreen?: boolean;
page: PageType;
}> = ({ page, children, widescreen = false }) => {
return (
<>
<Head>
<title>AI Safety Info</title>
</Head>
<main>
<main style={widescreen ? { maxWidth: "none" } : {}}>
<Header page={page} />
{children}
</main>
+1 -24
View File
@@ -1,33 +1,10 @@
import React from "react";
import { useState, useEffect } from "react";
import { initialQuestions } from "../settings";
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[] = [
"Are there any regulatory efforts aimed at addressing AI safety and alignment concerns?",
"How can I help with AI safety and alignment?",
"How could a predictive model - like an LLM - act like an agent?",
"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 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 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"?',
];
const SearchBoxInternal: React.FC<{
search: (
query: string,
+1 -126
View File
@@ -2,134 +2,9 @@ import { ChangeEvent } from "react";
import TextareaAutosize from "react-textarea-autosize";
import type { Parseable, LLMSettings, Entry, Mode } from "../types";
import { MODELS, ENCODERS } from "../hooks/useSettings";
import { SectionHeader, NumberInput, Slider } from "../components/html";
type LLMSettingsParsers = {
[key: string]:
| ((v: number | undefined) => any)
| ((v: string | undefined) => any)
| ((v: object | undefined) => any);
};
const DEFAULT_PROMPTS = {
context:
"You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
"using the following sources. Each source is labeled with a letter. Feel free to " +
"use the sources in any order, and try to use multiple sources in your answers.\n\n",
history:
"\n\n" +
'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
"These sources only apply to the last question. any sources used in previous answers " +
"are invalid.",
question:
"In your answer, please cite any claims you make back to each source " +
"using the format: [a], [b], etc. If you use multiple sources to make a claim " +
'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
modes: {
default: "",
concise:
"Answer very concisely, getting to the crux of the matter in as " +
"few words as possible. Limit your answer to 1-2 sentences.\n\n",
rookie:
"This user is new to the field of AI Alignment and Safety - don't " +
"assume they know any technical terms or jargon. Still give a complete answer " +
"without patronizing the user, but take any extra time needed to " +
"explain new concepts or to illustrate your answer with examples. " +
"Put extra effort into explaining the intuition behind concepts " +
"rather than just giving a formal definition.\n\n",
},
};
export const MODELS = {
"gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
"gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
"gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
/* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
};
export const ENCODERS = ["cl100k_base"];
/** Update the given `obj` so that it has `val` at the given path.
*
* e.g.
* updateIn({a: {b: 123}}, ['a', 'b', 'c'], 42) == {a: {b: 123, c: 42}}
* updateIn({a: {b: 123}}, ['z', 'y', 'x'], 42) == {a: {b: 123}, z: {y: {x: 42}}}
*/
export const updateIn = (
obj: { [key: string]: any },
[head, ...rest]: string[],
val: any
) => {
if (!head) {
// No path provided - do nothing
} else if (!rest || rest.length == 0) {
obj[head] = val;
} else {
if (obj[head] === undefined) {
obj[head] = {};
}
updateIn(obj[head], rest, val);
}
return obj;
};
/** Create a settings object in which all items in the `overrides` object will be parsed appropriately
*
* `parsers` should be an object mapping settings fields to functions that will return a valid setting.
* The parser functions should have default values that will be used if the provided value is undefined.
*/
const parseSettings = (overrides: LLMSettings, parsers: LLMSettingsParsers) =>
Object.entries(parsers).reduce(
(settings, [key, parser]) =>
updateIn(settings, [key], parser(overrides[key])),
{}
);
/** Make a parser function from the provided `defaultVal`.
*
* If the parsed value is undefined, `defaultVal` will be returned, otherwise it will be parsed as
* a value of the same type as `defaultVal`.
* If `defaultVal` is an object, it will return a parser that will recursively search for appropriate keys.
*/
const withDefault = (defaultVal: any) => {
if (typeof defaultVal === "number" && defaultVal % 1 === 0) {
return (v: string | undefined): number =>
v !== undefined ? parseInt(v, 10) : defaultVal;
} else if (typeof defaultVal === "number") {
return (v: string | undefined): number =>
v !== undefined ? parseFloat(v) : defaultVal;
} else if (typeof defaultVal === "object") {
const parsers = Object.entries(defaultVal).reduce(
(parsers, [key, val]) => updateIn(parsers, [key], withDefault(val)),
{}
);
return (v: object | undefined): object => parseSettings(v || {}, parsers);
} else {
return (v: any | undefined): any => v || defaultVal;
}
};
const SETTINGS_PARSERS = {
prompts: withDefault(DEFAULT_PROMPTS),
mode: (v: string | undefined) => (v || "default") as Mode,
completions: withDefault("gpt-3.5-turbo"),
encoder: withDefault("cl100k_base"),
topKBlocks: withDefault(MODELS["gpt-3.5-turbo"].topKBlocks), // the number of blocks to use as citations
maxNumTokens: withDefault(MODELS["gpt-3.5-turbo"].maxNumTokens),
tokensBuffer: withDefault(50), // the number of tokens to leave as a buffer when calculating remaining tokens
maxHistory: withDefault(10), // the max number of previous items to use as history
historyFraction: withDefault(0.25), // the (approximate) fraction of num_tokens to use for history text before truncating
contextFraction: withDefault(0.5), // the (approximate) fraction of num_tokens to use for context text before truncating
};
export const makeSettings = (overrides: LLMSettings) =>
parseSettings(
Object.entries(overrides).reduce(
(acc, [key, val]) => updateIn(acc, key.split("."), val),
{}
),
SETTINGS_PARSERS
);
type ChatSettingsParams = {
settings: LLMSettings;
changeSetting: (path: string[], value: any) => void;
+47
View File
@@ -0,0 +1,47 @@
import { useState } from "react";
import type { CurrentSearch, Citation } from "../types";
const updateCitations = (
allCitations: Citation[],
setCitations: (citations: Citation[]) => any,
entry?: CurrentSearch
) => {
if (!entry) return entry;
const entryCitations = Array.from(entry.citationsMap.values());
if (!entryCitations.some((c) => !c.index)) {
// All of the entries citations have indexes, so there weren't any changes since the last check
return entry;
}
// 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])
);
entryCitations.forEach((c) => {
const hash = c.title + c.url;
const index = citationsMapping[hash];
if (!index) {
c.index = allCitations.length + 1;
allCitations.push(c);
} else {
c.index = index;
}
});
setCitations(allCitations);
return entry;
};
export default function useCitations() {
const [citations, setCitations] = useState<Citation[]>([]);
const setEntryCitations = (entry: CurrentSearch) =>
updateCitations(citations, setCitations, entry);
return {
citations,
setEntryCitations,
};
}
+10 -6
View File
@@ -57,16 +57,19 @@ export async function* iterateData(res: Response) {
}
}
export const extractAnswer = async (
res: Response,
setCurrent: (e: CurrentSearch) => void
): Promise<SearchResult> => {
var result: AssistantEntry = {
const makeEntry = () =>
({
role: "assistant",
content: "",
citations: [],
citationsMap: new Map(),
};
} as AssistantEntry);
export const extractAnswer = async (
res: Response,
setCurrent: (e: CurrentSearch) => void
): Promise<SearchResult> => {
var result: AssistantEntry = makeEntry();
var followups: Followup[] = [];
for await (var data of iterateData(res)) {
switch (data.state) {
@@ -135,6 +138,7 @@ export const queryLLM = async (
sessionId: string,
controller: AbortController
): Promise<SearchResult> => {
setCurrent({ ...makeEntry(), phase: "started" });
// do SSE on a POST request.
const res = await fetchLLM(sessionId, query, settings, history, controller);
+172
View File
@@ -0,0 +1,172 @@
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import type { CurrentSearch, Mode, Entry, LLMSettings } from "../types";
type LLMSettingsParsers = {
[key: string]:
| ((v: number | undefined) => any)
| ((v: string | undefined) => any)
| ((v: object | undefined) => any);
};
const DEFAULT_PROMPTS = {
context:
"You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
"using the following sources. Each source is labeled with a letter. Feel free to " +
"use the sources in any order, and try to use multiple sources in your answers.\n\n",
history:
"\n\n" +
'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
"These sources only apply to the last question. any sources used in previous answers " +
"are invalid.",
question:
"In your answer, please cite any claims you make back to each source " +
"using the format: [a], [b], etc. If you use multiple sources to make a claim " +
'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
modes: {
default: "",
concise:
"Answer very concisely, getting to the crux of the matter in as " +
"few words as possible. Limit your answer to 1-2 sentences.\n\n",
rookie:
"This user is new to the field of AI Alignment and Safety - don't " +
"assume they know any technical terms or jargon. Still give a complete answer " +
"without patronizing the user, but take any extra time needed to " +
"explain new concepts or to illustrate your answer with examples. " +
"Put extra effort into explaining the intuition behind concepts " +
"rather than just giving a formal definition.\n\n",
},
};
export const MODELS = {
"gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
"gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
"gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
/* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
};
export const ENCODERS = ["cl100k_base"];
/** Update the given `obj` so that it has `val` at the given path.
*
* e.g.
* updateIn({a: {b: 123}}, ['a', 'b', 'c'], 42) == {a: {b: 123, c: 42}}
* updateIn({a: {b: 123}}, ['z', 'y', 'x'], 42) == {a: {b: 123}, z: {y: {x: 42}}}
*/
export const updateIn = (
obj: { [key: string]: any },
[head, ...rest]: string[],
val: any
) => {
if (!head) {
// No path provided - do nothing
} else if (!rest || rest.length == 0) {
obj[head] = val;
} else {
if (obj[head] === undefined) {
obj[head] = {};
}
updateIn(obj[head], rest, val);
}
return obj;
};
/** Create a settings object in which all items in the `overrides` object will be parsed appropriately
*
* `parsers` should be an object mapping settings fields to functions that will return a valid setting.
* The parser functions should have default values that will be used if the provided value is undefined.
*/
const parseSettings = (overrides: LLMSettings, parsers: LLMSettingsParsers) =>
Object.entries(parsers).reduce(
(settings, [key, parser]) =>
updateIn(settings, [key], parser(overrides[key])),
{}
);
/** Make a parser function from the provided `defaultVal`.
*
* If the parsed value is undefined, `defaultVal` will be returned, otherwise it will be parsed as
* a value of the same type as `defaultVal`.
* If `defaultVal` is an object, it will return a parser that will recursively search for appropriate keys.
*/
const withDefault = (defaultVal: any) => {
if (typeof defaultVal === "number" && defaultVal % 1 === 0) {
return (v: string | undefined): number =>
v !== undefined ? parseInt(v, 10) : defaultVal;
} else if (typeof defaultVal === "number") {
return (v: string | undefined): number =>
v !== undefined ? parseFloat(v) : defaultVal;
} else if (typeof defaultVal === "object") {
const parsers = Object.entries(defaultVal).reduce(
(parsers, [key, val]) => updateIn(parsers, [key], withDefault(val)),
{}
);
return (v: object | undefined): object => parseSettings(v || {}, parsers);
} else {
return (v: any | undefined): any => v || defaultVal;
}
};
const SETTINGS_PARSERS = {
prompts: withDefault(DEFAULT_PROMPTS),
mode: (v: string | undefined) => (v || "default") as Mode,
completions: withDefault("gpt-3.5-turbo"),
encoder: withDefault("cl100k_base"),
topKBlocks: withDefault(MODELS["gpt-3.5-turbo"].topKBlocks), // the number of blocks to use as citations
maxNumTokens: withDefault(MODELS["gpt-3.5-turbo"].maxNumTokens),
tokensBuffer: withDefault(50), // the number of tokens to leave as a buffer when calculating remaining tokens
maxHistory: withDefault(10), // the max number of previous items to use as history
historyFraction: withDefault(0.25), // the (approximate) fraction of num_tokens to use for history text before truncating
contextFraction: withDefault(0.5), // the (approximate) fraction of num_tokens to use for context text before truncating
};
export const makeSettings = (overrides: LLMSettings) =>
parseSettings(
Object.entries(overrides).reduce(
(acc, [key, val]) => updateIn(acc, key.split("."), val),
{}
),
SETTINGS_PARSERS
);
type ChatSettingsParams = {
settings: LLMSettings;
changeSetting: (path: string[], value: any) => void;
};
export default function useSettings() {
const [settings, updateSettings] = useState<LLMSettings>(makeSettings({}));
const router = useRouter();
const updateInUrl = (path: string[], value: any) =>
router.replace({
pathname: router.pathname,
query: {
...router.query,
[path.join(".")]: value.toString(),
},
});
const changeSetting = (path: string[], value: any) => {
updateInUrl(path, value);
updateSettings((settings) => ({ ...updateIn(settings, path, value) }));
};
const setMode = (mode: Mode | undefined) => {
if (mode) {
updateSettings({ ...settings, mode: mode });
localStorage.setItem("chat_mode", mode);
}
};
useEffect(() => {
const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
updateSettings(makeSettings({ ...router.query, mode: mode }));
}, [updateSettings, router]);
return {
settings,
changeSetting,
setMode,
};
}
+4 -10
View File
@@ -3,6 +3,7 @@ import { useState, useEffect } from "react";
import Link from "next/link";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import useSettings from "../hooks/useSettings";
import type { Mode } from "../types";
import Page from "../components/page";
import Chat from "../components/chat";
@@ -12,23 +13,16 @@ const MAX_FOLLOWUPS = 4;
const Home: NextPage = () => {
const [sessionId, setSessionId] = useState("");
const [mode, setMode] = useState<[Mode, boolean]>(["default", false]);
// store mode in localstorage
useEffect(() => {
if (mode[1]) localStorage.setItem("chat_mode", mode[0]);
}, [mode]);
const { settings, setMode } = useSettings();
// initial load
useEffect(() => {
const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
setMode([mode, true]);
setSessionId(crypto.randomUUID());
}, []);
return (
<Page page="index">
<Controls mode={mode} setMode={setMode} />
<Controls mode={settings.mode || "default"} setMode={setMode} />
<h2 className="bg-red-100 text-red-800">
<b>WARNING</b>: This is a very <b>early prototype</b>.{" "}
@@ -38,7 +32,7 @@ const Home: NextPage = () => {
welcomed.
</h2>
<Chat sessionId={sessionId} settings={{ mode: mode[0] }} />
<Chat sessionId={sessionId} settings={{ mode: settings.mode }} />
</Page>
);
};
+24 -64
View File
@@ -1,84 +1,44 @@
import type { NextPage } from "next";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import Head from "next/head";
import Page from "../components/page";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import type { Mode, Entry, LLMSettings } from "../types";
import Header from "../components/header";
import useSettings from "../hooks/useSettings";
import type { Entry } from "../types";
import Chat from "../components/chat";
import { Controls } from "../components/controls";
import {
ChatSettings,
ChatPrompts,
updateIn,
makeSettings,
} from "../components/settings";
import { ChatSettings, ChatPrompts } from "../components/settings";
const Playground: NextPage = () => {
const [sessionId, setSessionId] = useState("");
const [settings, updateSettings] = useState<LLMSettings>(makeSettings({}));
const [query, setQuery] = useState<string>("");
const [history, setHistory] = useState<Entry[]>([]);
const router = useRouter();
const setMode = (mode: [Mode, boolean]) => {
if (mode[1]) {
localStorage.setItem("chat_mode", mode[0]);
updateSettings((settings) => ({ ...settings, mode: mode[0] }));
}
};
const changeSetting = (path: string[], value: any) => {
router.replace(
{
pathname: router.pathname,
query: {
...router.query,
[path.join(".")]: value.toString(),
},
},
undefined,
{ scroll: false, shallow: true }
);
updateSettings((settings) => ({ ...updateIn(settings, path, value) }));
};
const { settings, changeSetting, setMode } = useSettings();
// initial load
useEffect(() => {
const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
updateSettings(makeSettings(router.query));
setMode([mode, true]);
setSessionId(crypto.randomUUID());
}, [updateSettings, router]);
}, []);
return (
<>
<Head>
<title>AI Safety Info</title>
</Head>
<main style={{ maxWidth: "none" }}>
<Header page="playground" />
<Controls mode={[settings.mode || "default", true]} setMode={setMode} />
<div className="flex">
<ChatPrompts
settings={settings}
query={query}
history={history}
changeSetting={changeSetting}
/>
<Chat
sessionId={sessionId}
settings={settings}
onQuery={setQuery}
onNewEntry={setHistory}
/>
<ChatSettings settings={settings} changeSetting={changeSetting} />
</div>
</main>
</>
<Page page="playground" widescreen={true}>
<Controls mode={settings.mode || "default"} setMode={setMode} />
<div className="flex">
<ChatPrompts
settings={settings}
query={query}
history={history}
changeSetting={changeSetting}
/>
<Chat
sessionId={sessionId}
settings={settings}
onQuery={setQuery}
onNewEntry={setHistory}
/>
<ChatSettings settings={settings} changeSetting={changeSetting} />
</div>
</Page>
);
};
+24
View File
@@ -3,3 +3,27 @@ export const API_URL =
export const STAMPY_URL = process.env.STAMPY_URL || "https://aisafety.info";
export const STAMPY_CONTENT_URL =
process.env.STAMPY_CONTENT_URL || `${API_URL}/human`;
// initial questions to fill the search box with.
export const initialQuestions: string[] = [
"Are there any regulatory efforts aimed at addressing AI safety and alignment concerns?",
"How can I help with AI safety and alignment?",
"How could a predictive model - like an LLM - act like an agent?",
"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 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 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"?',
];
+1 -1
View File
@@ -23,7 +23,7 @@ export type UserEntry = {
export type AssistantEntry = {
role: "assistant";
content: string;
citations: Citation[];
citations?: Citation[];
citationsMap: Map<string, Citation>;
deleted?: boolean;
};