mirror of
https://github.com/wassname/stampy-chat.git
synced 2026-09-09 11:36:23 +08:00
fix typescript errors
This commit is contained in:
+7
-2
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
})}} />;
|
||||
}
|
||||
}
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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>Goodhart’s 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>Goodhart’s 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>Goodhart’s 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>Goodhart’s 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
@@ -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
@@ -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
@@ -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`;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user