Skip to content

Commit

Permalink
Connect chat feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
mruwnik committed May 3, 2024
1 parent 235ff71 commit b68589b
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 17 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
| sed s/{CODA_INCOMING_TOKEN}/${{ secrets.CODA_INCOMING_TOKEN }}/ \
| sed s/{CODA_WRITES_TOKEN}/${{ secrets.CODA_WRITES_TOKEN }}/ \
| sed s/{GOOGLE_ANALYTICS_ID}/${{ secrets.GOOGLE_ANALYTICS_ID }}/ \
| sed s/{DISCORD_LOGGING_URL}/${{ secrets.DISCORD_LOGGING_URL }}/ \
> wrangler.toml
npm ci
npm run deploy
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
| sed s/{CODA_INCOMING_TOKEN}/${{ secrets.CODA_INCOMING_TOKEN }}/ \
| sed s/{CODA_WRITES_TOKEN}/${{ secrets.CODA_WRITES_TOKEN }}/ \
| sed s/{GOOGLE_ANALYTICS_ID}/${{ secrets.GOOGLE_ANALYTICS_ID }}/ \
| sed s/{DISCORD_LOGGING_URL}/${{ secrets.DISCORD_LOGGING_URL }}/ \
> wrangler.toml
npm ci
npm run deploy
Expand Down
10 changes: 8 additions & 2 deletions app/components/Chatbot/ChatEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Link} from '@remix-run/react'
import MarkdownIt from 'markdown-it'
import QuestionMarkIcon from '~/components/icons-generated/QuestionMark'
import Contents from '~/components/Article/Contents'
import Feedback from '~/components/Feedback'
import Feedback, {logFeedback} from '~/components/Feedback'
import useGlossary from '~/hooks/useGlossary'
import './chat_entry.css'
import type {Entry, AssistantEntry, StampyEntry, Citation, ErrorMessage} from '~/hooks/useChat'
Expand Down Expand Up @@ -136,7 +136,7 @@ const Reference = (citation: Citation) => {
)
}

const ChatbotReply = ({phase, content, citationsMap}: AssistantEntry) => {
const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry) => {
const citations = [] as Citation[]
citationsMap?.forEach((v) => {
citations.push(v)
Expand Down Expand Up @@ -193,6 +193,9 @@ const ChatbotReply = ({phase, content, citationsMap}: AssistantEntry) => {
pageid="chatbot"
upHint="This response was helpful"
downHint="This response was unhelpful"
onSubmit={async (message: string, option?: string) =>
logFeedback({message, option, type: 'bot', question, answer: content, citations})
}
options={[
'Making things up',
'Wrong subject',
Expand Down Expand Up @@ -227,6 +230,9 @@ const StampyArticle = ({pageid, content, title}: StampyEntry) => {
pageid={pageid}
upHint="This response was helpful"
downHint="This response was unhelpful"
onSubmit={async (message: string, option?: string) =>
logFeedback({message, option, type: 'human', question: title, answer: content, pageid})
}
options={[
'Making things up',
'Wrong subject',
Expand Down
11 changes: 4 additions & 7 deletions app/components/Chatbot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {

// Add a new history entry, replacing the previous one if it was canceled
const message = {content: question, role: 'user'} as Entry
const answer = {role: 'assistant', question} as AssistantEntry
setHistory((current) => {
const last = current[current.length - 1]
if (
Expand All @@ -254,15 +255,11 @@ export const Chatbot = ({question, questions, settings}: ChatbotProps) => {
(last?.role === 'stampy' && last?.content) ||
['error'].includes(last?.role)
) {
return [...current, message, {role: 'assistant'} as AssistantEntry]
return [...current, message, answer]
} else if (last?.role === 'user' && last?.content === question) {
return [...current.slice(0, current.length - 1), {role: 'assistant'} as AssistantEntry]
return [...current.slice(0, current.length - 1), answer]
}
return [
...current.slice(0, current.length - 2),
message,
{role: 'assistant'} as AssistantEntry,
]
return [...current.slice(0, current.length - 2), message, answer]
})

setFollowups(undefined)
Expand Down
8 changes: 6 additions & 2 deletions app/components/Feedback/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import useOutsideOnClick from '~/hooks/useOnOutsideClick'
import './feedback.css'

export type FeedbackFormProps = {
onSubmit?: (msg: string, option?: string) => Promise<any>
onClose?: () => void
options?: string[]
}
const FeedbackForm = ({onClose, options}: FeedbackFormProps) => {
const FeedbackForm = ({onSubmit, onClose, options}: FeedbackFormProps) => {
const [selected, setSelected] = useState<string>()
const [message, setMessage] = useState('')
const [enabledSubmit, setEnabledSubmit] = useState(!options)
const [numClicks, setNumClicks] = useState(0)
const clickCheckerRef = useOutsideOnClick(onClose)
Expand All @@ -28,7 +30,8 @@ const FeedbackForm = ({onClose, options}: FeedbackFormProps) => {
setEnabledSubmit(true)
}

const handleSubmit = () => {
const handleSubmit = async () => {
onSubmit && (await onSubmit(message, selected))
onClose && onClose()
}

Expand Down Expand Up @@ -56,6 +59,7 @@ const FeedbackForm = ({onClose, options}: FeedbackFormProps) => {
name="feedback-text"
className={['feedback-text bordered', !options ? 'no-options' : ''].join(' ')}
placeholder="Leave a comment (optional)"
onChange={(e) => setMessage(e.target.value)}
/>
<Button className="primary full-width" action={handleSubmit} disabled={!enabledSubmit}>
<p>Submit feedback</p>
Expand Down
31 changes: 30 additions & 1 deletion app/components/Feedback/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ import {CompositeButton} from '~/components/Button'
import {Action, ActionType} from '~/routes/questions.actions'
import './feedback.css'
import FeedbackForm from './Form'
import type {Citation} from '~/hooks/useChat'

type FeedbackType = {
option?: string
message?: string
question?: string
answer: string
pageid?: string
citations?: Citation[]
type: 'human' | 'bot' | 'error'
}
export const logFeedback = async (feedback: FeedbackType) =>
fetch(`/chat/log`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(feedback),
})

type FeedbackProps = {
pageid: string
Expand All @@ -11,8 +30,17 @@ type FeedbackProps = {
upHint?: string
downHint?: string
options?: string[]
onSubmit?: (message: string, option?: string) => Promise<any>
}
const Feedback = ({pageid, showForm, labels, upHint, downHint, options}: FeedbackProps) => {
const Feedback = ({
pageid,
showForm,
labels,
upHint,
downHint,
options,
onSubmit,
}: FeedbackProps) => {
const [showFeedback, setShowFeedback] = useState(false)
const [showFeedbackForm, setShowFeedbackForm] = useState(false)

Expand Down Expand Up @@ -47,6 +75,7 @@ const Feedback = ({pageid, showForm, labels, upHint, downHint, options}: Feedbac

{showFeedbackForm && (
<FeedbackForm
onSubmit={onSubmit}
onClose={() => {
setShowFeedback(true)
setShowFeedbackForm(false)
Expand Down
14 changes: 10 additions & 4 deletions app/hooks/useChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type UserEntry = {

export type AssistantEntry = {
role: 'assistant'
question?: string
content: string
citations?: Citation[]
citationsMap?: Map<string, Citation>
Expand Down Expand Up @@ -82,6 +83,7 @@ export type EntryRole = 'error' | 'stampy' | 'assistant' | 'user' | 'deleted'
export type HistoryEntry = {
role: EntryRole
content: string
question?: string
}

export const formatCitations: (text: string) => string = (text) => {
Expand Down Expand Up @@ -181,29 +183,32 @@ export async function* iterateData(res: Response) {
}
}

const makeEntry = () =>
const makeEntry = (question?: string) =>
({
role: 'assistant',
question,
content: '',
citations: [],
citationsMap: new Map(),
}) as AssistantEntry

export const extractAnswer = async (
question: string | undefined,
res: Response,
setCurrent: (e: AssistantEntry) => void
): Promise<SearchResult> => {
const formatResponse = (result: AssistantEntry, data: Entry) => {
const content = formatCitations((result?.content || '') + data.content)
return {
content,
question,
role: 'assistant',
citations: result?.citations || [],
citationsMap: findCitations(content, result?.citations || []),
} as AssistantEntry
}

let result: AssistantEntry = makeEntry()
let result: AssistantEntry = makeEntry(question)
let followups: Followup[] = []
for await (const data of iterateData(res)) {
switch (data.state) {
Expand Down Expand Up @@ -264,7 +269,8 @@ export const queryLLM = async (
controller: AbortController,
settings?: ChatSettings
): Promise<SearchResult> => {
setCurrent({...makeEntry(), phase: 'started'})
const question = history[history.length - 1]?.content
setCurrent({...makeEntry(question), phase: 'started'})
// do SSE on a POST request.
const res = await fetchLLM(sessionId, history, controller, settings)

Expand All @@ -275,7 +281,7 @@ export const queryLLM = async (
}

try {
return await extractAnswer(res, setCurrent)
return await extractAnswer(question, res, setCurrent)
} catch (e) {
if ((e as Error)?.name === 'AbortError') {
return {result: {role: 'error', content: 'aborted'}}
Expand Down
54 changes: 54 additions & 0 deletions app/routes/chat.log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type {ActionFunctionArgs} from '@remix-run/cloudflare'
import type {Citation} from '~/hooks/useChat'

const formatCitation = (citation: Citation) => {
const items = (['title', 'date', 'url', 'source', 'text'] as (keyof Citation)[])
.map(
(key) =>
`\t\t<div>\n\t\t\t<span>${key}</span>\n\t\t\t<span>${citation[key]}</span>\n\t\t</div>`
)
.join('\n')
return `\t<div>\n${items}\n\t</div>`
}
const formatAnswer = (question?: string, answer?: string, citations?: Citation[]) => {
const formattedCitations = citations?.map(formatCitation).join('\n')
return `<html>
<h1 class="question">${question || '<no question>'}</h1>
<article>
<div class="answer">${answer}</div>
<div class="citations">${formattedCitations}</div>
</article>
</html>`
}

export const action = async ({request}: ActionFunctionArgs) => {
const {question, answer, citations, type, message, option, pageid} = (await request.json()) as any

try {
// Convert the large string content to a Blob
const fileBlob = new Blob([formatAnswer(question, answer, citations)], {type: 'text/plain'})

// Prepare the form data
const formData = new FormData()
formData.append('file', fileBlob, 'question_and_response.html')

const info = [
['Type', type],
['Pageid', pageid && `[${pageid}](https://aisafety.info/questions/${pageid}/${question})`],
['Selected option', option],
['Feedback', message],
]
.filter(([_, val]) => val && val.trim())
.map(([item, val]) => `* ${item} - ${val}`)
.join('\n')
formData.append('payload_json', JSON.stringify({content: `Chat feedback:\n${info}`}))

const response = await fetch(`${DISCORD_LOGGING_URL}`, {method: 'POST', body: formData})
if (!response.ok) {
throw new Error(`Failed to post message: ${response.status} ${response.statusText}`)
}
} catch (error) {
console.error('Failed to post message:', error)
}
return null
}
1 change: 1 addition & 0 deletions remix.env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ declare const CODA_WRITES_TOKEN: string
declare const NLP_SEARCH_ENDPOINT: string
declare const ALLOW_ORIGINS: string
declare const GOOGLE_ANALYTICS_ID: string
declare const DISCORD_LOGGING_URL: string
3 changes: 2 additions & 1 deletion wrangler.toml.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ CODA_INCOMING_TOKEN = "{CODA_INCOMING_TOKEN}"
CODA_WRITES_TOKEN = "{CODA_WRITES_TOKEN}"
NLP_SEARCH_ENDPOINT = "https://stampy-nlp-t6p37v2uia-uw.a.run.app/"
ALLOW_ORIGINS = "https://chat.aisafety.info"
GOOGLE_ANALYTICS_ID = "{GOOGLE_ANALYTICS_ID}"
GOOGLE_ANALYTICS_ID = "{GOOGLE_ANALYTICS_ID}"
DISCORD_LOGGING_URL = "{DISCORD_LOGGING_URL}"

0 comments on commit b68589b

Please sign in to comment.