Skip to content

Commit

Permalink
Merge pull request #711 from StampyAI/sundry-fixes
Browse files Browse the repository at this point in the history
properly display popup content
  • Loading branch information
zarSou9 committed Jun 10, 2024
2 parents 2901be3 + a01aa26 commit 6593238
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 74 deletions.
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"?
]

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

0 comments on commit 6593238

Please sign in to comment.