Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

properly display popup content #711

Merged
merged 3 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/assets/icons/exclamation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 11 additions & 7 deletions app/components/Article/Contents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string | null =

if (!footnote) return null

const elem = document.createElement('div')
elem.innerHTML = footnote.innerHTML
const elem = footnote.cloneNode(true) as Element

// remove the back link, as it's useless in the popup
if (elem?.firstElementChild?.lastChild)
elem.firstElementChild.removeChild(elem.firstElementChild.lastChild)
Array.from(elem.getElementsByClassName('footnote-backref')).forEach((e) => {
e.parentElement?.removeChild(e)
})

return elem.innerHTML
return elem.firstElementChild?.innerHTML || null
}

const addPopup = (e: HTMLElement, id: string, contents: string, mobile?: boolean): HTMLElement => {
Expand Down Expand Up @@ -81,6 +81,10 @@ const glossaryInjecter = (pageid: string, glossary: Glossary) => {
}

const insertGlossary = (pageid: string, glossary: Glossary) => {
// Generate a random ID for these glossary items. This is needed when mulitple articles are displayed -
// gloassary items should be only displayed once per article, but this is checked by popup id, so if
// there are 2 articles that have the same glossary item, then only the first articles popups would work
const randomId = Math.floor(1000 + Math.random() * 9000).toString()
const injecter = glossaryInjecter(pageid, glossary)

return (textNode: Node) => {
Expand Down Expand Up @@ -129,11 +133,11 @@ const insertGlossary = (pageid: string, glossary: Glossary) => {
const image = entry.image && `<img src="${entry.image}"/>`
addPopup(
e as HTMLSpanElement,
`glossary-${entry.term}`,
`glossary-${entry.term}-${randomId}`,
`<div class="glossary-popup flex-container black small">
<div class="contents ${image ? '' : 'full-width'}">
<div class="small-bold">${entry.term}</div>
<div class="defintion small">${entry.contents}</div>
<div class="definition small">${entry.contents}</div>
${link || ''}
</div>
${image || ''}
Expand Down
2 changes: 1 addition & 1 deletion app/components/Article/article.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ article .contents a.button {
article .link-popup .glossary-popup > .contents {
padding: var(--spacing-24) var(--spacing-40) var(--spacing-24);
}
article .defintion {
article .definition {
height: 140px;
display: -webkit-box;
/* These are webkit specific things, so might not work in all browsers (firefox handles them fine) */
Expand Down
21 changes: 16 additions & 5 deletions app/components/Chatbot/ChatEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,17 @@ const Reference = (citation: Citation) => {
)
}

const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry) => {
const ChatbotReply = ({
question,
phase,
content,
citationsMap,
no,
}: AssistantEntry & {no: number}) => {
const mobile = useIsMobile()
const citations = [] as Citation[]
citationsMap?.forEach((v) => {
citations.push(v)
citations.push({...v, id: `${v.id}-${no}`})
})
citations.sort((a, b) => a.index - b.index)
const phaseMessageClass = 'phase-message large-reading'
Expand Down Expand Up @@ -216,7 +222,7 @@ const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry)
if (chunk?.match(/(\[\d+\])/)) {
const refId = chunk.slice(1, chunk.length - 1)
const ref = citationsMap?.get(refId)
return ref && <ReferenceLink key={i} mobile={mobile} {...ref} />
return ref && <ReferenceLink key={i} mobile={mobile} {...ref} id={`${ref.id}-${no}`} />
} else if (chunk === '\n') {
return <br key={i} />
} else {
Expand Down Expand Up @@ -253,18 +259,23 @@ const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry)
)
}

const StampyArticle = ({pageid, content, title}: StampyEntry) => {
const StampyArticle = ({pageid, content, title, no}: StampyEntry & {no: number}) => {
const glossary = useGlossary()
const hint = `This response is pulled from our article "${title}" which was written by members of AISafety.info`

const uniqueReferences = (content: string, idFinder: string) =>
content
.replaceAll(new RegExp(`id="(${idFinder})"`, 'g'), `id="$1-${no}"`)
.replaceAll(new RegExp(`href="#(${idFinder})"`, 'g'), `href="#$1-${no}"`)

return (
<div>
<Title title="Stampy" Icon={StampyIcon} answerType="human" hint={hint} />
<div className="answer">
<article className="stampy">
<Contents
pageid={pageid || ''}
html={content || 'Loading...'}
html={uniqueReferences(content || 'Loading...', 'fn\\d+-.*?')}
glossary={glossary || {}}
/>
</article>
Expand Down
4 changes: 4 additions & 0 deletions app/components/Chatbot/chat_entry.css
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ article.stampy {
transition-delay: 0s;
}

.chat-entry article {
min-height: inherit;
}

@media (max-width: 780px) {
.title-inner-container {
flex-direction: column;
Expand Down
74 changes: 25 additions & 49 deletions app/components/Chatbot/index.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,20 @@
import {useEffect, useRef, useState} from 'react'
import {useFetcher, useNavigate} from '@remix-run/react'
import ExclamationIcon from '../icons-generated/Exclamation'
import IconStampyLarge from '~/components/icons-generated/StampyLarge'
import IconStampySmall from '~/components/icons-generated/StampySmall'
import SendIcon from '~/components/icons-generated/PlaneSend'
import Button from '~/components/Button'
import {queryLLM, Entry, AssistantEntry, StampyEntry, Followup, ChatSettings} from '~/hooks/useChat'
import useOutsideOnClick from '~/hooks/useOnOutsideClick'
import useOnSiteQuestions from '~/hooks/useOnSiteQuestions'
import ChatEntry from './ChatEntry'
import './widgit.css'
import {questionUrl} from '~/routesMapper'
import {Question} from '~/server-utils/stampy'
import {useSearch} from '~/hooks/useSearch'
import Input from '~/components/Input'

// to be replaced with actual pool questions
const poolQuestions = [
{text: 'Do people seriously worry about existential risk from AI?', pageid: '6953'},
{text: 'Is AI safety about systems becoming malevolent or conscious?', pageid: '6194'},
{text: 'When do experts think human-level AI will be created?', pageid: '5633'},
{text: 'Why is AI alignment a hard problem?', pageid: '8163'},
{
text: 'Why can’t we just “put the AI in a box” so that it can’t influence the outside world?',
pageid: '6176',
},
{
text: 'What are the differences between AGI, transformative AI, and superintelligence?',
pageid: '5864',
},
{text: 'What are large language models?', pageid: '5864'},
{text: "Why can't we just turn the AI off if it starts to misbehave?", pageid: '3119'},
{text: 'What is instrumental convergence?', pageid: '897I'},
{text: "What is Goodhart's law?", pageid: '8185'},
{text: 'What is the orthogonality thesis?', pageid: '6568'},
{text: 'How powerful would a superintelligence become?', pageid: '7755'},
{text: 'Will AI be able to think faster than humans?', pageid: '8E41'},
{text: "Isn't the real concern misuse?", pageid: '9B85'},
{text: 'Are AIs conscious?', pageid: '8V5J'},
{
text: 'What are the differences between a singularity, an intelligence explosion, and a hard takeoff?',
pageid: '8IHO',
},
{text: 'What is an intelligence explosion?', pageid: '6306'},
{text: 'How might AGI kill people?', pageid: '5943'},
{text: 'What is a "warning shot"?', pageid: '7748'},
]

const MIN_SIMILARITY = 0.85

type QuestionInputProps = {
Expand Down Expand Up @@ -98,7 +68,7 @@ const QuestionInput = ({
<p className="default">{results[0].title}</p>
</Button>
) : undefined}
<div className="flex-container">
<div className="relative">
<Input
placeholder={placeholder}
className="large full-width shadowed"
Expand All @@ -115,18 +85,18 @@ const QuestionInput = ({
<SendIcon className="send pointer" onClick={() => handleAsk(question)} />
</div>
{fixed && <div className="white-space"></div>}

<div className="mobile-only grey padding-top-8">
<ExclamationIcon /> <span>Stampy can be inaccurate. Always verify its sources.</span>
</div>
</div>
)
}

export const WidgetStampy = ({className}: {className?: string}) => {
const [question, setQuestion] = useState('')
const {selected: questions} = useOnSiteQuestions()
const navigate = useNavigate()
const questions = [
'What is AI Safety?',
'How would the AI even get out in the world?',
'Do people seriously worry about existential risk from AI?',
]

const stampyUrl = (question: string) => `/chat/?question=${question.trim()}`
return (
Expand All @@ -140,10 +110,10 @@ export const WidgetStampy = ({className}: {className?: string}) => {
<IconStampySmall />
<div className="sample-messages rounded">
<div className="padding-bottom-24">Try asking me...</div>
{questions.map((question, i) => (
{questions.map(({title}, i) => (
<div key={i} className="padding-bottom-16">
<Button className="secondary-alt-large" action={stampyUrl(question)}>
{question}
<Button className="secondary-alt-large" action={stampyUrl(title)}>
{title}
</Button>
</div>
))}
Expand Down Expand Up @@ -171,10 +141,14 @@ type FollowupsProps = {
className?: string
}
const Followups = ({title, followups, onSelect, className}: FollowupsProps) => {
const {randomQuestions} = useOnSiteQuestions()
const items =
(followups?.length || 0) >= 3
? followups
: [...(followups || []), ...poolQuestions.sort(() => Math.random() - 0.5)].slice(0, 3)
: [
...(followups || []),
...randomQuestions().map(({title, pageid}) => ({text: title, pageid})),
].slice(0, 3)
return (
<>
{title && <div className={'padding-bottom-24 grey' + (className || '')}>{title}</div>}
Expand Down Expand Up @@ -216,12 +190,11 @@ const SplashScreen = ({

type ChatbotProps = {
question?: string
questions?: Followup[]
questions?: Question[]
settings?: ChatSettings
}
export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
const [followups, setFollowups] = useState<Followup[]>()

const [sessionId] = useState(crypto.randomUUID())
const [history, setHistory] = useState([] as Entry[])
const [controller, setController] = useState(() => new AbortController())
Expand Down Expand Up @@ -337,10 +310,13 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
return (
<div className="centered col-10 height-70">
{history.length === 0 ? (
<SplashScreen questions={questions} onSelection={showArticleByID} />
<SplashScreen
questions={questions?.map(({title, pageid}) => ({pageid, text: title}))}
onSelection={showArticleByID}
/>
) : undefined}
{history.map((item, i) => (
<ChatEntry key={`chat-entry-${i}`} {...item} />
<ChatEntry key={`chat-entry-${i}`} {...item} no={i} />
))}

<div className="padding-bottom-128">
Expand All @@ -360,9 +336,9 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
fixed
/>

<div className={'warning-floating'}>
<p className={'xs'}>
<span className={'red xs-bold'}>Caution! </span>
<div className="desktop-only warning-floating">
<p className="xs">
<span className="red xs-bold">Caution! </span>
This is an early prototype. Don’t automatically trust what it says, and make sure to
follow its sources.
</p>
Expand Down
16 changes: 16 additions & 0 deletions app/components/icons-generated/Exclamation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type {SVGProps} from 'react'
const SvgExclamation = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" width={16} height={16} fill="none" {...props}>
<path
fill="#D40000"
d="M8.5 11a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M7.5 5v4a.5.5 0 0 0 1 0V5a.5.5 0 0 0-1 0"
/>
<path
fill="#D40000"
fillRule="evenodd"
d="M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12m0-1A5 5 0 1 0 8 3a5 5 0 0 0 0 10"
clipRule="evenodd"
/>
</svg>
)
export default SvgExclamation
1 change: 1 addition & 0 deletions app/components/icons-generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {default as EclipseIndividual} from './EclipseIndividual'
export {default as EclipseTeam} from './EclipseTeam'
export {default as Edit} from './Edit'
export {default as Ellipsis} from './Ellipsis'
export {default as Exclamation} from './Exclamation'
export {default as Flag} from './Flag'
export {default as Followup} from './Followup'
export {default as GetInvolvedMobile} from './GetInvolvedMobile'
Expand Down
2 changes: 1 addition & 1 deletion app/hooks/useChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type Citation = {
id?: string
}

export type Entry = UserEntry | AssistantEntry | ErrorMessage | StampyEntry
export type Entry = (UserEntry | AssistantEntry | ErrorMessage | StampyEntry) & {no?: number}
export type ChatPhase =
| 'started'
| 'semantic'
Expand Down
44 changes: 44 additions & 0 deletions app/hooks/useOnSiteQuestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {Question} from '~/server-utils/stampy'
import {useOnSiteQuestions as getQuestions} from './useCachedObjects'
import {useEffect, useState} from 'react'

const topQuestions = [
'6953', // Do people seriously worry about existential risk from AI? debugger eval code:1:26
'6194', // Is AI safety about systems becoming malevolent or conscious? debugger eval code:1:26
'5633', // When do experts think human-level AI will be created? debugger eval code:1:26
'8163', // Why is AI alignment a hard problem? debugger eval code:1:26
'6176', // Why can’t we just “put the AI in a box” so that it can’t influence the outside world? debugger eval code:1:26
'5864', // What are the differences between AGI, transformative AI, and superintelligence? debugger eval code:1:26
'5864', // What are large language models? debugger eval code:1:26
'3119', // Why can't we just turn the AI off if it starts to misbehave? debugger eval code:1:26
'897I', // What is instrumental convergence? debugger eval code:1:26
'8185', // What is Goodhart's law? debugger eval code:1:26
'6568', // What is the orthogonality thesis? debugger eval code:1:26
'7755', // How powerful would a superintelligence become? debugger eval code:1:26
'8E41', // Will AI be able to think faster than humans? debugger eval code:1:26
'9B85', // Isn't the real concern misuse? debugger eval code:1:26
'8V5J', // Are AIs conscious? debugger eval code:1:26
'8IHO', // What are the differences between a singularity, an intelligence explosion, and a hard takeoff? debugger eval code:1:26
'6306', // What is an intelligence explosion? debugger eval code:1:26
'5943', // How might AGI kill people? debugger eval code:1:26
'7748', // What is a "warning shot"?
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big fan


const useOnSiteQuestions = () => {
const onSite = getQuestions()
const [selected, setSelected] = useState([] as Question[])

const top = onSite.items?.filter((i) => topQuestions.includes(i.pageid))
const randomQuestions = (n?: number) =>
top?.sort(() => 0.5 - Math.random()).slice(0, n || 3) || []

useEffect(() => {
if (!selected?.length) {
setSelected(randomQuestions())
}
// eslint-disable-next-line
}, [onSite.items])

return {...onSite, topQuestions: top, selected, randomQuestions}
}
export default useOnSiteQuestions
2 changes: 1 addition & 1 deletion app/hooks/useSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useState, useEffect, useRef} from 'react'
import {Question} from '~/server-utils/stampy'
import {useOnSiteQuestions} from './useCachedObjects'
import useOnSiteQuestions from './useOnSiteQuestions'

const NUM_RESULTS = 8

Expand Down
Empty file removed app/hooks/useTags.ts
Empty file.
Loading
Loading