diff --git a/packages/chat-component/src/main.ts b/packages/chat-component/src/main.ts index 7ddc7ad3..f9312e97 100644 --- a/packages/chat-component/src/main.ts +++ b/packages/chat-component/src/main.ts @@ -16,6 +16,7 @@ import iconDoubleCheck from '../public/svg/doublecheck-icon.svg?inline'; import iconCopyToClipboard from '../public/svg/copy-icon.svg?inline'; import iconSend from '../public/svg/send-icon.svg?inline'; import iconClose from '../public/svg/close-icon.svg?inline'; +import iconQuestion from '../public/svg/question-icon.svg?inline'; /** * A chat component that allows the user to ask questions and get answers from an API. @@ -385,20 +386,29 @@ export class ChatComponent extends LitElement { // render followup questions if (followupQuestions && followupQuestions.length > 0) { return html` - +
+ ${globalConfig.FOLLOW_UP_QUESTIONS_LABEL_TEXT} + +
`; } diff --git a/packages/chat-component/src/style.ts b/packages/chat-component/src/style.ts index b9112e2b..bc78656f 100644 --- a/packages/chat-component/src/style.ts +++ b/packages/chat-component/src/style.ts @@ -9,12 +9,13 @@ export const mainStyle = css` --text-color: #123f58; --primary-color: rgba(241, 255, 165, 0.6); --white: #fff; + --black: #111111; --light-gray: #e3e3e3; --dark-gray: #4e5288; - --accent-high: #0040ff; + --accent-high: #b200ff; --accent-dark: #002b23; --accent-light: #e6fbf7; - --accent-lighter: rgba(140, 222, 242, 0.4); + --accent-lighter: rgba(242, 140, 241, 0.4); --error-color: #8a0000; } :host(.dark) { @@ -73,8 +74,8 @@ export const mainStyle = css` display: flex; width: 100%; height: 0; - background: var(--accent-dark); - z-index: 1; + background: var(--black); + z-index: 2; opacity: 0.8; transition: all 0.3s ease-in-out; } @@ -147,7 +148,7 @@ export const mainStyle = css` .aside { top: 30px; left: auto; - z-index: 2; + z-index: 3; background: var(--white); display: block; padding: 20px; @@ -164,7 +165,10 @@ export const mainStyle = css` position: sticky; bottom: 0; z-index: 1; + border-radius: 10px; background: var(--secondary-color); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + padding: 15px 10px 50px; } .form__label { display: block; @@ -185,7 +189,7 @@ export const mainStyle = css` border-bottom: 4px solid var(--accent-high); } .aside__link:not(.active):hover { - border-bottom: 4px solid var(--accent-light); + border-bottom: 4px solid var(--accent-lighter); cursor: pointer; } .aside__link { @@ -338,23 +342,30 @@ export const mainStyle = css` .user-message .chat__txt--info { text-align: right; } + .items__listWrapper { + border-top: 1px dotted var(--light-gray); + display: flex; + } + .items__listWrapper .icon { + padding-top: 35px; + opacity: 0.3; + } .items__list.followup { display: flex; flex-direction: row; padding: 20px; list-style-type: none; flex-wrap: wrap; - border-top: 1px dotted var(--light-gray); } .items__list { - margin-top: 10px; + margin: 10px 0; display: block; } .items__listItem--followup { cursor: pointer; - padding: 0 5px; - border-radius: 5px; - border: 1px solid var(--accent-high); + padding: 0 15px; + border-radius: 15px; + border: 2px solid var(--accent-high); margin: 5px; } .items__listItem--citation { @@ -387,6 +398,7 @@ export const mainStyle = css` text-decoration: none; color: var(--text-color); display: block; + font-size: 1.2rem; } .defaults__list { list-style-type: none; @@ -424,6 +436,7 @@ export const mainStyle = css` transition: all 0.3s ease-in-out; } .defaults__span { + color: var(--accent-high); font-weight: bold; display: block; margin-top: 20px; diff --git a/packages/chat-component/src/utils/index.ts b/packages/chat-component/src/utils/index.ts index 7b05476a..a5653f73 100644 --- a/packages/chat-component/src/utils/index.ts +++ b/packages/chat-component/src/utils/index.ts @@ -1,19 +1,17 @@ // Util functions to process text from response, clean it up and style it // We keep it in this util file because we may not need it once we introduce // a new response format with TypeChat or a similar component -import { NEXT_QUESTION_INDICATOR } from '../config/global-config.js'; // Let's give the response a type so we can use it in the component export function processText(inputText: string, arrays: Array | Array>): ProcessTextReturn { // Keeping all the regex at this level so they can be easily changed or removed - const nextQuestionIndicator = NEXT_QUESTION_INDICATOR; + const nextQuestionMatch = `Next questions:|<<([^>]+)>>`; const findCitations = /\[(.*?)]/g; - const findFollowingSteps = /steps:(.*?)(?:Next Questions:|<<|$)/s; + const findFollowingSteps = /:(.*?)(?:Follow-up questions:|Next questions:|<<|$)/s; const findNextQuestions = /Next Questions:(.*?)$/s; const findQuestionsbyDoubleArrow = /<<([^<>]+)>>/g; - const findNumberedItems = /\d+\.\s+/; - + const findNumberedItems = /^\d+\.\s/; // Find and process citations const citation: NonNullable = {}; let citations: Citation[] = []; @@ -24,7 +22,7 @@ export function processText(inputText: string, arrays: Array | Arr if (!citation[citationText]) { citation[citationText] = referenceCounter++; } - return `[${citation[citationText]}]`; + return `${citation[citationText]}`; }); citations = Object.keys(citation).map((text, index) => ({ ref: index + 1, @@ -34,36 +32,29 @@ export function processText(inputText: string, arrays: Array | Arr // Because the format for followup questions is inconsistent // and sometimes it includes a Next Questions prefix, we need do some extra work - const nextQuestionsIndex = replacedText.indexOf(nextQuestionIndicator); - const hasNextQuestions = nextQuestionsIndex !== -1; + const hasNextQuestions = replacedText.includes(nextQuestionMatch); // Find and store 'follow this steps' portion of the response // considering the fact that sometimes the 'next questions' indicator is present // and sometimes it's not const followingStepsMatch = replacedText.match(findFollowingSteps); const followingStepsText = followingStepsMatch ? followingStepsMatch[1].trim() : ''; const followingSteps = followingStepsText.split('\n').filter(Boolean); - arrays[1] = followingSteps; + const cleanFollowingSteps = followingSteps.map((item) => { + return item.replace(findNumberedItems, ''); + }); + arrays[1] = cleanFollowingSteps; // Determine which regex to use, depending if the indicator is present const nextRegex = hasNextQuestions ? findNextQuestions : findQuestionsbyDoubleArrow; const nextQuestionsMatch = replacedText.match(nextRegex); - const nextQuestionsText = nextQuestionsMatch ? nextQuestionsMatch[1].trim() : ''; let nextQuestions: string[] = []; - // Find and store 'follow up questions' portion of the response - if (hasNextQuestions) { - // Remove the 'Next Questions' prefix from the response - replacedText = replacedText.replace(nextQuestionIndicator, ''); - nextQuestions = nextQuestionsText.split(findNumberedItems).filter(Boolean); - } else { - nextQuestions = nextQuestionsText.split('\n').filter(Boolean); - nextQuestions = cleanUpFollowUp(nextQuestions); - } + nextQuestions = cleanUpFollowUp([...(nextQuestionsMatch as string[])]); // Remove the 'steps', 'citation' and 'next questions' portions of the response // from the response answer - const stepsIndex = replacedText.indexOf('steps:'); + const stepsIndex = replacedText.indexOf('s:'); // eslint-disable-next-line unicorn/no-negated-condition, unicorn/prefer-string-slice - replacedText = stepsIndex !== -1 ? inputText.substring(0, stepsIndex + 6) : inputText; + replacedText = stepsIndex !== -1 ? inputText.substring(0, stepsIndex + 4) : inputText; arrays[2] = nextQuestions; return { replacedText, arrays };