Skip to content

Commit

Permalink
Merge branch 'stampy-redesign' into Ticket-#591
Browse files Browse the repository at this point in the history
  • Loading branch information
zarSou9 authored Jun 4, 2024
2 parents 423eb8c + 23d78c4 commit bcd5fdd
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 73 deletions.
1 change: 1 addition & 0 deletions app/components/Article/article.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ article .link-popup {
visibility: hidden;
opacity: 0;
position: absolute;
max-width: 400px;
display: inline-block;
font: var(--baseFont);
font-style: normal;
Expand Down
2 changes: 1 addition & 1 deletion app/components/Article/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const Banner = ({title, text, icon, backgroundColour, textColour}: BannerType) =
return (
<div
key={title}
className="banner rounded"
className="banner rounded shadowed"
style={{
backgroundColor: backgroundColour || 'inherit',
color: textColour || 'inherit',
Expand Down
10 changes: 5 additions & 5 deletions app/components/Chatbot/ChatEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ComponentType} from 'react'
import {ComponentType, ReactNode} from 'react'
import {Link} from '@remix-run/react'
import MarkdownIt from 'markdown-it'
import Contents from '~/components/Article/Contents'
Expand Down Expand Up @@ -33,20 +33,20 @@ const AnswerInfo = ({
{answerType === 'human' ? 'Human-written' : 'Bot-generated'} response
</span>
<QuestionMarkIcon className="hint" />
<div className="hint-contents rounded">{hint}</div>
<div className="hint-contents bordered">{hint}</div>
</span>
)
}

type TitleProps = {
title: string
Icon: ComponentType
Icon: ({width, height}: {width: string; height: string}) => ReactNode
answerType?: 'human' | 'bot' | 'error'
hint?: string
}
const Title = ({title, Icon, answerType, hint}: TitleProps) => (
<div className="flex-container title padding-bottom-16">
<Icon />
<Icon width="40" height="40" />
<span className="default-bold flex-double">{title}</span>
<AnswerInfo answerType={answerType} hint={hint} />
</div>
Expand Down Expand Up @@ -102,7 +102,7 @@ const ReferencePopup = (citation: Citation) => {
const parsed = citation.text?.match(/^###.*?###\s+"""(.*?)"""$/ms)
if (!parsed) return undefined
return (
<div className="reference-contents rounded">
<div className="reference-contents bordered">
<ReferenceSummary {...citation} titleClass="large-bold" />
<div className="grey padding-bottom-16 padding-top-24">Referenced excerpt</div>
<div
Expand Down
1 change: 1 addition & 0 deletions app/components/Chatbot/chat_entry.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ article.stampy {
position: absolute;
transform: translateY(var(--spacing-24));
visibility: hidden;
width: 256px;
}

.chat-entry .hint-contents:hover,
Expand Down
109 changes: 65 additions & 44 deletions app/components/Chatbot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 ChatEntry from './ChatEntry'
import './widgit.css'
import {questionUrl} from '~/routesMapper'
Expand All @@ -14,35 +15,34 @@ import Input from '~/components/Input'

// to be replaced with actual pool questions
const poolQuestions = [
{title: 'Do people seriously worry about existential risk from AI?', pageid: '6953'},
{title: 'Is AI safety about systems becoming malevolent or conscious?', pageid: '6194'},
{title: 'When do experts think human-level AI will be created?', pageid: '5633'},
{title: 'Why is AI alignment a hard problem?', pageid: '8163'},
{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'},
{
title: 'Why can’t we just “put the AI in a box” so that it can’t influence the outside world?',
text: 'Why can’t we just “put the AI in a box” so that it can’t influence the outside world?',
pageid: '6176',
},
{
title: 'What are the differences between AGI, transformative AI, and superintelligence?',
text: 'What are the differences between AGI, transformative AI, and superintelligence?',
pageid: '5864',
},
{title: 'What are large language models?', pageid: '5864'},
{title: "Why can't we just turn the AI off if it starts to misbehave?", pageid: '3119'},
{title: 'What is instrumental convergence?', pageid: '897I'},
{title: "What is Goodhart's law?", pageid: '8185'},
{title: 'What is the orthogonality thesis?', pageid: '6568'},
{title: 'How powerful would a superintelligence become?', pageid: '7755'},
{title: 'Will AI be able to think faster than humans?', pageid: '8E41'},
{title: "Isn't the real concern misuse?", pageid: '9B85'},
{title: 'Are AIs conscious?', pageid: '8V5J'},
{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'},
{
title:
'What are the differences between a singularity, an intelligence explosion, and a hard takeoff?',
text: 'What are the differences between a singularity, an intelligence explosion, and a hard takeoff?',
pageid: '8IHO',
},
{title: 'What is an intelligence explosion?', pageid: '6306'},
{title: 'How might AGI kill people?', pageid: '5943'},
{title: 'What is a "warning shot"?', pageid: '7748'},
{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
Expand All @@ -52,27 +52,33 @@ type QuestionInputProps = {
onChange?: (val: string) => void
onAsk?: (val: string) => void
fixed?: boolean
placeholder?: string
}
const QuestionInput = ({initial, onChange, onAsk, fixed}: QuestionInputProps) => {
const QuestionInput = ({
initial,
onChange,
onAsk,
fixed,
placeholder = 'Ask Stampy a question...',
}: QuestionInputProps) => {
const [question, setQuestion] = useState(initial || '')
const [placeholder, setPlaceholder] = useState('Ask Stampy a question...')
const {results, search, clear} = useSearch(1)
const clickDetectorRef = useOutsideOnClick(() => handleChange(''))

const handleAsk = (val: string) => {
clear()
onAsk && onAsk(val)
setQuestion('')
setPlaceholder('Message Stampy')
}

const handleChange = (val: string) => {
search(val, MIN_SIMILARITY)
search(val, 0.7)
setQuestion(val)
onChange && onChange(val)
}

return (
<div className={'widget-ask ' + (fixed ? 'fixed col-10' : 'col-9')}>
<div className={'widget-ask ' + (fixed ? 'fixed col-10' : 'col-9')} ref={clickDetectorRef}>
{results.length > 0 ? (
<Button className="full-width suggestion" action={() => handleAsk(results[0].title)}>
<p className="default">{results[0].title}</p>
Expand All @@ -85,7 +91,9 @@ const QuestionInput = ({initial, onChange, onAsk, fixed}: QuestionInputProps) =>
value={question}
onChange={(e) => handleChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && question.trim() && onAsk) {
if (e.key === 'Escape') {
handleChange('')
} else if (e.key === 'Enter' && question.trim() && onAsk) {
handleAsk(question)
}
}}
Expand Down Expand Up @@ -148,19 +156,28 @@ type FollowupsProps = {
onSelect: (followup: Followup) => void
className?: string
}
const Followups = ({title, followups, onSelect, className}: FollowupsProps) => (
<>
{title && <div className={'padding-bottom-24 grey' + (className || '')}>{title}</div>}
const Followups = ({title, followups, onSelect, className}: FollowupsProps) => {
const items =
(followups?.length || 0) >= 3
? followups
: [...(followups || []), ...poolQuestions.sort(() => Math.random() - 0.5)].slice(0, 3)
return (
<>
{title && <div className={'padding-bottom-24 grey' + (className || '')}>{title}</div>}

{followups?.map(({text, pageid}, i) => (
<div key={i} className="padding-bottom-16">
<Button className="secondary-alt-large" action={() => onSelect({text, pageid})}>
{text}
</Button>
</div>
))}
</>
)
{items?.map(({text, pageid}, i) => (
<div key={i} className="padding-bottom-16">
<Button
className="secondary-alt-large text-align-left"
action={() => onSelect({text, pageid})}
>
{text}
</Button>
</div>
))}
</>
)
}

const SplashScreen = ({
questions,
Expand Down Expand Up @@ -191,12 +208,11 @@ type ChatbotProps = {
export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
const [followups, setFollowups] = useState<Followup[]>()

// FIXME: Generate session id
const [sessionId] = useState(crypto.randomUUID())
const [history, setHistory] = useState([] as Entry[])
const [controller, setController] = useState(() => new AbortController())
const fetcher = useFetcher({key: 'followup-fetcher'})
const {search, resultsForRef, waitForResults} = useSearch(1)
const {search, resultsForRef, waitForResults, loadedQuestions} = useSearch(1)

useEffect(() => {
if (!fetcher.data || fetcher.state !== 'idle') return
Expand All @@ -213,7 +229,7 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
// check proper insertion of pool questions
// question.relatedQuestions = question.relatedQuestions.slice(0,2);
setFollowups(
[...(question.relatedQuestions || []), ...poolQuestions.sort(() => Math.random() - 0.5)]
(question.relatedQuestions || [])
.slice(0, 3)
.map(({title, pageid}) => ({text: title, pageid}))
)
Expand Down Expand Up @@ -294,11 +310,12 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {

const fetchFlag = useRef(false)
useEffect(() => {
if (question && !fetchFlag.current) {
if (loadedQuestions && question && !fetchFlag.current) {
fetchFlag.current = true
onQuestion(question)
}
})
// eslint-disable-next-line
}, [loadedQuestions, question])

return (
<div className="centered col-10 height-70">
Expand All @@ -319,7 +336,11 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
) : undefined}
</div>

<QuestionInput onAsk={onQuestion} fixed />
<QuestionInput
onAsk={onQuestion}
placeholder={history.length > 0 ? 'Message Stampy' : undefined}
fixed
/>

<div className={'warning-floating'}>
<p className={'xs'}>
Expand Down
3 changes: 3 additions & 0 deletions app/components/Chatbot/widgit.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

.widget-ask {
position: relative;

}
.widget-ask.fixed {
position: fixed;
Expand Down Expand Up @@ -57,6 +58,8 @@
border: 1px solid var(--colors-cool-grey-200, #dfe3e9);
justify-content: start;
padding: var(--spacing-8) var(--spacing-12);
position: absolute;
top: calc(-1 * var(--spacing-56));
}

.widget-ask .suggestion:hover {
Expand Down
23 changes: 4 additions & 19 deletions app/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,16 @@ import './input.css'
type InputProps = {
className?: string
disabled?: boolean
placeHolder?: string
placeholder?: string
value?: string
onChange?: (e: any) => void
onKeyDown?: (e: any) => void
onBlur?: (e: any) => void
}
const Input = ({
className,
disabled = false,
placeHolder,
value,
onChange,
onKeyDown,
}: InputProps) => {
const Input = ({className, ...props}: InputProps) => {
const classes = ['input', className].filter((i) => i).join(' ')

return (
<input
className={classes}
disabled={disabled}
placeholder={placeHolder}
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
/>
)
return <input className={classes} {...props} />
}

export default Input
7 changes: 5 additions & 2 deletions app/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface MenuItemProps {
onMouseEnter?: () => void
onMouseLeave?: () => void
id?: string
reload?: boolean
}
export const MenuItem = ({
primary = false,
Expand All @@ -22,10 +23,12 @@ export const MenuItem = ({
onMouseEnter,
onMouseLeave,
id,
reload,
}: MenuItemProps) => {
const Component = !reload ? Link : (props: {to: string}) => <a href={props.to} {...props} />
return (
<li className="top-menu-item" id={id} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<Link to={link} className="top-menu-link">
<Component to={link} className="top-menu-link">
{icon ? (
typeof icon === 'string' ? (
<img loading="lazy" src={icon} className="top-menu-icon" alt={text} />
Expand All @@ -35,7 +38,7 @@ export const MenuItem = ({
) : null}

<span className={['top-menu-text', primary ? '' : 'teal-500'].join(' ')}>{text}</span>
</Link>
</Component>
</li>
)
}
Expand Down
8 changes: 7 additions & 1 deletion app/components/Nav/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ export const Nav = ({toc, categories}: NavProps) => {
id="showArticles"
/>
<ArticlesDropdown toc={toc} categories={categories || []} />
<MenuItem primary={true} link="/chat/" icon={<BotIcon />} text="AI Safety Chatbot" />
<MenuItem
primary={true}
link="/chat/"
icon={<BotIcon />}
text="AI Safety Chatbot"
reload
/>
<li className="top-menu-item">
<div className="top-menu-divider"></div>
</li>
Expand Down
4 changes: 3 additions & 1 deletion app/hooks/useSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const useSearch = (numResults = NUM_RESULTS) => {

const search = (userQuery: string, minSimilarity?: number) => {
isPendingSearch.current = true
const wordCount = userQuery.split(' ').length
const wordCount = userQuery.trim().split(' ').length
if (wordCount > 2 && tfWorkerRef.current) {
if (runningQueryRef.current || !tfWorkerRef.current) {
searchLater(userQuery, minSimilarity)
Expand Down Expand Up @@ -215,6 +215,8 @@ export const useSearch = (numResults = NUM_RESULTS) => {
}

return {
loadedQuestions: !!items,
loadedEmbeddings: !!runningQueryRef.current,
search,
clear,
results,
Expand Down
Loading

0 comments on commit bcd5fdd

Please sign in to comment.