From eec067333eb03acd3efdc0e741b347cc9d48e419 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell Date: Sat, 8 Jun 2024 14:02:24 +0200 Subject: [PATCH] properly display popup content --- app/assets/icons/exclamation.svg | 5 +++++ app/components/Article/Contents.tsx | 18 +++++++++------- app/components/Article/article.css | 2 +- app/components/Chatbot/ChatEntry.tsx | 21 ++++++++++++++----- app/components/Chatbot/chat_entry.css | 4 ++++ app/components/Chatbot/index.tsx | 15 ++++++++----- .../icons-generated/Exclamation.tsx | 16 ++++++++++++++ app/components/icons-generated/index.ts | 1 + app/hooks/useChat.ts | 2 +- 9 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 app/assets/icons/exclamation.svg create mode 100644 app/components/icons-generated/Exclamation.tsx diff --git a/app/assets/icons/exclamation.svg b/app/assets/icons/exclamation.svg new file mode 100644 index 00000000..3f736b9a --- /dev/null +++ b/app/assets/icons/exclamation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/components/Article/Contents.tsx b/app/components/Article/Contents.tsx index c0ae2d26..500edb86 100644 --- a/app/components/Article/Contents.tsx +++ b/app/components/Article/Contents.tsx @@ -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 => { @@ -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) => { @@ -129,11 +133,11 @@ const insertGlossary = (pageid: string, glossary: Glossary) => { const image = entry.image && `` addPopup( e as HTMLSpanElement, - `glossary-${entry.term}`, + `glossary-${entry.term}-${randomId}`, `
${entry.term}
-
${entry.contents}
+
${entry.contents}
${link || ''}
${image || ''} diff --git a/app/components/Article/article.css b/app/components/Article/article.css index 2163894d..c0e60b3f 100644 --- a/app/components/Article/article.css +++ b/app/components/Article/article.css @@ -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) */ diff --git a/app/components/Chatbot/ChatEntry.tsx b/app/components/Chatbot/ChatEntry.tsx index 8f40e4e4..7127625e 100644 --- a/app/components/Chatbot/ChatEntry.tsx +++ b/app/components/Chatbot/ChatEntry.tsx @@ -170,11 +170,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' @@ -214,7 +220,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 && + return ref && } else if (chunk === '\n') { return
} else { @@ -251,10 +257,15 @@ 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 (
@@ -262,7 +273,7 @@ const StampyArticle = ({pageid, content, title}: StampyEntry) => { <article className="stampy"> <Contents pageid={pageid || ''} - html={content || 'Loading...'} + html={uniqueReferences(content || 'Loading...', 'fn\\d+-.*?')} glossary={glossary || {}} /> </article> diff --git a/app/components/Chatbot/chat_entry.css b/app/components/Chatbot/chat_entry.css index e8d7a2c1..fef25fc9 100644 --- a/app/components/Chatbot/chat_entry.css +++ b/app/components/Chatbot/chat_entry.css @@ -176,3 +176,7 @@ article.stampy { visibility: visible; transition-delay: 0s; } + +.chat-entry article { + min-height: inherit; +} diff --git a/app/components/Chatbot/index.tsx b/app/components/Chatbot/index.tsx index 062d1dcf..abe3b852 100644 --- a/app/components/Chatbot/index.tsx +++ b/app/components/Chatbot/index.tsx @@ -12,6 +12,7 @@ import {questionUrl} from '~/routesMapper' import {Question} from '~/server-utils/stampy' import {useSearch} from '~/hooks/useSearch' import Input from '~/components/Input' +import {Exclamation} from '../icons-generated' // to be replaced with actual pool questions const poolQuestions = [ @@ -98,7 +99,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" @@ -115,6 +116,10 @@ const QuestionInput = ({ <SendIcon className="send pointer" onClick={() => handleAsk(question)} /> </div> {fixed && <div className="white-space"></div>} + + <div className="mobile-only grey padding-top-8"> + <Exclamation /> <span>Stampy can be inaccurate. Always verify its sources.</span> + </div> </div> ) } @@ -340,7 +345,7 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => { <SplashScreen questions={questions} 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"> @@ -360,9 +365,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> diff --git a/app/components/icons-generated/Exclamation.tsx b/app/components/icons-generated/Exclamation.tsx new file mode 100644 index 00000000..fb2afffd --- /dev/null +++ b/app/components/icons-generated/Exclamation.tsx @@ -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 diff --git a/app/components/icons-generated/index.ts b/app/components/icons-generated/index.ts index 7c2e13b4..51d2f9a6 100644 --- a/app/components/icons-generated/index.ts +++ b/app/components/icons-generated/index.ts @@ -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' diff --git a/app/hooks/useChat.ts b/app/hooks/useChat.ts index 69747950..975ef5cd 100644 --- a/app/hooks/useChat.ts +++ b/app/hooks/useChat.ts @@ -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'