Skip to content

Commit

Permalink
human answers
Browse files Browse the repository at this point in the history
  • Loading branch information
mruwnik committed Apr 8, 2024
1 parent ab15416 commit 3cfb88c
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 78 deletions.
170 changes: 170 additions & 0 deletions app/components/Chatbot/ChatEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {ComponentType} from 'react'
import {Link} from '@remix-run/react'
import QuestionMarkIcon from '~/components/icons-generated/QuestionMark'
import BotIcon from '~/components/icons-generated/Bot'
import PersonIcon from '~/components/icons-generated/Person'
import StampyIcon from '~/components/icons-generated/Stampy'
import Contents from '~/components/Article/Contents'
import useGlossary from '~/hooks/useGlossary'
import './chat_entry.css'
import type {Entry, AssistantEntry, StampyEntry, Citation} from '~/hooks/useChat'

const hints = {
bot: 'bla bla bla something bot',
human: 'bla bla bla by humans',
}

const AnswerInfo = ({answerType}: {answerType?: 'human' | 'bot'}) => {
if (!answerType) return null
return (
<span className="info">
{answerType === 'human' ? <PersonIcon /> : <BotIcon />}
<span className="small grey">
{answerType === 'human' ? 'Human-written' : 'Bot-generated'} response
</span>
<QuestionMarkIcon className="hint" />
<div className="hint-contents">{hints[answerType]}</div>
</span>
)
}

type TitleProps = {
title: string
Icon: ComponentType
answerType?: 'human' | 'bot'
}
const Title = ({title, Icon, answerType}: TitleProps) => (
<div className="flex-container title">
<Icon />
<span className="default-bold flex-double">{title}</span>
<AnswerInfo answerType={answerType} />
</div>
)

const UserQuery = ({content}: Entry) => (
<div>
<Title title="You" Icon={PersonIcon} />
<div>{content}</div>
</div>
)

// FIXME: this id should be unique across the page - I doubt it will be now
const ReferenceLink = ({id, reference}: {id: string; reference: string}) => (
<Link id={`#${id}-ref`} to={`#${id}`} className="reference-link">
{reference}
</Link>
)

const Reference = ({id, title, authors, source, url, reference}: Citation) => {
const referenceSources = {
arxiv: 'Scientific paper',
blogs: 'Blogpost',
'aisafety.info': 'AISafety.info',
}

const Authors = ({authors}: {authors?: string[]}) => {
if (!authors || !authors.length || authors.length === 0) return null
return (
<span className="authors">
{authors.slice(0, 3).join(', ')}
{authors.length <= 3 ? '' : ' et. al.'}
</span>
)
}

return (
<div key={id} id={`#${id}`} className="reference padding-bottom-32">
<div className="reference-num small">{reference}</div>
<div>
<div className="title">{title}</div>
<div>
<Authors authors={authors} />
<span>{' · '}</span>
<Link className="source-link" to={url}>
{referenceSources[source as keyof typeof referenceSources] || url}
</Link>
</div>
</div>
</div>
)
}

const ChatbotReply = ({phase, content, citationsMap}: AssistantEntry) => {
const citations = [] as Citation[]
citationsMap?.forEach((v) => {
citations.push(v)
})

const references = citations.map(({reference}) => reference).join('')
const referencesRegex = new RegExp(`(\\[[${references}]\\])`)

const PhaseState = () => {
switch (phase) {
case 'started':
return <p>Loading: Sending query...</p>
case 'semantic':
return <p>Loading: Performing semantic search...</p>
case 'history':
return <p>Loading: Processing history...</p>
case 'context':
return <p>Loading: Creating context...</p>
case 'prompt':
return <p>Loading: Creating prompt...</p>
case 'llm':
return <p>Loading: Waiting for LLM...</p>
case 'streaming':
case 'followups':
default:
return null
}
}

return (
<div>
<Title title="Stampy" Icon={StampyIcon} answerType="bot" />
<PhaseState />
<div>
{content?.split(referencesRegex).map((chunk, i) => {
if (chunk.match(referencesRegex)) {
const ref = citationsMap?.get(chunk[1])
return <ReferenceLink key={i} id={ref?.id || chunk[i]} reference={chunk[1]} />
} else {
return <span key={i}>{chunk}</span>
}
})}
</div>
{citations?.map(Reference)}
{phase === 'followups' ? <p>Checking for followups...</p> : undefined}
</div>
)
}

const StampyArticle = ({pageid, content}: StampyEntry) => {
const glossary = useGlossary()

return (
<div>
<Title title="Stampy" Icon={StampyIcon} answerType="human" />
<article className="stampy">
<Contents pageid={pageid || ''} html={content || 'Loading...'} glossary={glossary || {}} />
</article>
</div>
)
}

const ChatEntry = (props: Entry) => {
const roles = {
user: UserQuery,
stampy: StampyArticle,
assistant: ChatbotReply,
} as {[k: string]: ComponentType<Entry>}
const Role = roles[props.role] as ComponentType<Entry>
if (!Role) return null
return (
<div className="chat-entry padding-bottom-40">
<Role {...props} />
</div>
)
}

export default ChatEntry
53 changes: 53 additions & 0 deletions app/components/Chatbot/chat_entry.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
article.stampy {
background: var(--colors-light-grey);
padding: var(--spacing-40);
border-radius: var(--spacing-6);
max-width: unset;
margin: var(--spacing-16);
margin-left: var(--spacing-56);
}

.chat-entry .title {
display: flex;
gap: var(--spacing-16);
}

.chat-entry .info {
display: flex;
gap: var(--spacing-6);
align-items: center;
}

.chat-entry .hint-contents {
position: absolute;
transform: translateY(var(--spacing-24));
visibility: hidden;
}

.chat-entry .hint:hover + .hint-contents {
visibility: visible;
background: red;
}

.chat-entry .reference-link {
background: red;
width: var(--spacing-16);
height: var(--spacing-16);
display: inline-block;
}

.reference {
display: flex;
gap: var(--spacing-16);
}
.reference .reference-num {
width: var(--spacing-32);
height: var(--spacing-32);
background: red;
border-radius: 6px;
text-align: center;
}

.reference .source-link {
color: var(--colors-teal-500);
}
Loading

0 comments on commit 3cfb88c

Please sign in to comment.