Skip to content

Commit e3e3a48

Browse files
committed
feat: Accept agenticConfiguration prop to accommodate agentic behaviors.
1 parent 65fdfeb commit e3e3a48

11 files changed

Lines changed: 530 additions & 108 deletions

File tree

docs/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/index.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { HeaderLogo } from "./components/HeaderLogo";
2828
import { ConfigurationDrawer } from "components/ConfigurationDrawer";
2929
import "./ui/_index.scss";
3030
import "./index.scss";
31-
import { RerankerId } from "../../src/types";
31+
import { AgenticResponse, RerankerId } from "../../src/types";
3232

3333
const formatStringProp = (value?: string) => {
3434
if (!value) {
@@ -235,6 +235,33 @@ const App = () => {
235235
rerankerId={rerankerId}
236236
lambda={lambda}
237237
enableStreaming={isStreamingEnabled}
238+
agenticConfiguration={{
239+
url: "https://vectara-com-chatbot-agent-server.onrender.com/verify-prospect",
240+
onAgenticResponse: (response: AgenticResponse) => {
241+
if (response.event === "prompt_schedule_sales") {
242+
return {
243+
message: "Would you like to connect with the Vectara Sales team?",
244+
userActionOptions: [
245+
{
246+
label: "Schedule a demo",
247+
message: "I'd like to connect with sales"
248+
}
249+
]
250+
};
251+
}
252+
253+
if (response.event === "handle_prospect_decline") {
254+
return {
255+
message: "No problem! Let us know when you're ready. Looking forward to helping you out!"
256+
};
257+
}
258+
259+
if (response.event === "schedule_sales") {
260+
return { message: "In a live context, this would connect you to the Vectara Sales team." };
261+
}
262+
}
263+
}}
264+
requestSource="react-chatbot-docs"
238265
/>
239266

240267
<VuiSpacer size="m" />

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"dependencies": {
3333
"@types/react": "^17.0.0",
3434
"@types/react-dom": "^17.0.0",
35-
"@vectara/stream-query-client": "^3.2.0",
35+
"@vectara/stream-query-client": "^5.1.0",
3636
"classnames": "^2.3.2",
3737
"lodash": "^4.17.21",
3838
"prismjs": "^1.29.0",

src/components/ChatItem.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,12 @@ export const ChatItem = ({ question, answer, searchResults, factualConsistencySc
114114
</span>
115115
)}
116116
</VuiText>
117-
118117
{factualConsistencyScore && (
119118
<>
120119
<VuiSpacer size="xs" />
121120
{factualConsistencyScore}
122121
</>
123122
)}
124-
125123
{reorderedSearchResults && reorderedSearchResults.length > 0 && (
126124
<>
127125
<VuiSpacer size="s" />
@@ -140,9 +138,11 @@ export const ChatItem = ({ question, answer, searchResults, factualConsistencySc
140138

141139
return (
142140
<>
143-
<div className="vrcbChatMessageContainer vrcbChatMessageContainer--question">
144-
<div className="vrcbChatMessage">{question}</div>
145-
</div>
141+
{question && (
142+
<div className="vrcbChatMessageContainer vrcbChatMessageContainer--question">
143+
<div className="vrcbChatMessage">{question}</div>
144+
</div>
145+
)}
146146

147147
<VuiSpacer size="xs" />
148148

src/components/ChatView.tsx

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { Fragment, ReactNode, useEffect, useMemo, useRef, useState } from "react";
2-
import { VuiButtonSecondary, VuiContextProvider, VuiFlexContainer, VuiFlexItem, VuiSpacer } from "../vui";
2+
import {
3+
VuiButtonSecondary,
4+
VuiContextProvider,
5+
VuiFlexContainer,
6+
VuiFlexItem,
7+
VuiSpacer,
8+
VuiTopicButton
9+
} from "../vui";
310
import { QueryInput } from "./QueryInput";
411
import { ChatItem } from "./ChatItem";
512
import { useChat } from "../useChat";
613
import { Loader } from "./Loader";
714
import { MinimizeIcon } from "./Icons";
815
import { FactualConsistencyBadge } from "./FactualConsistencyBadge";
916
import { ExampleQuestions } from "./exampleQuestions/ExampleQuestions";
10-
import {RerankerId, SummaryLanguage} from "types";
17+
import { AgenticConfiguration, ChatActionOption, RerankerId, SummaryLanguage } from "types";
1118

1219
const inputSizeToQueryInputSize = {
1320
large: "l",
@@ -66,6 +73,13 @@ export interface Props {
6673

6774
// Enables streaming responses from the API. Defaults to true.
6875
enableStreaming?: boolean;
76+
77+
// Enables the chatbot to modify its behavior by sending requests to an agentic service.
78+
agenticConfiguration?: AgenticConfiguration;
79+
80+
// A string that allows the Vectara platform to track where chat requests are coming from.
81+
// This could be an application name, for example.
82+
requestSource?: string;
6983
}
7084

7185
/**
@@ -91,6 +105,8 @@ export const ChatView = ({
91105
rerankerId,
92106
lambda,
93107
enableStreaming = true,
108+
agenticConfiguration,
109+
requestSource
94110
}: Props) => {
95111
const [isOpen, setIsOpen] = useState<boolean>(isInitiallyOpen ?? false);
96112
const [query, setQuery] = useState<string>("");
@@ -105,7 +121,9 @@ export const ChatView = ({
105121
summaryPromptName,
106122
rerankerId,
107123
lambda,
108-
enableStreaming
124+
enableStreaming,
125+
agenticConfiguration,
126+
requestSource
109127
});
110128

111129
const appLayoutRef = useRef<HTMLDivElement>(null);
@@ -150,38 +168,66 @@ export const ChatView = ({
150168

151169
const historyItems = useMemo(
152170
() =>
153-
messageHistory.map((turn, index) => {
154-
const { question, answer, results, factualConsistencyScore } = turn;
155-
const onRetry =
156-
hasError && index === messageHistory.length - 1
157-
? () => sendMessage({ query: question, isRetry: true })
158-
: undefined;
159-
160-
return (
161-
<Fragment key={index}>
162-
<ChatItem
163-
question={question}
164-
answer={answer}
165-
searchResults={results}
166-
factualConsistencyScore={
167-
enableFactualConsistencyScore && <FactualConsistencyBadge score={factualConsistencyScore} />
168-
}
169-
onRetry={onRetry}
170-
/>
171-
{index < messageHistory.length - 1 && <VuiSpacer size="m" />}
172-
</Fragment>
173-
);
171+
messageHistory.map((messageHistoryItem, index) => {
172+
if (messageHistoryItem.type === "action") {
173+
const { options } = messageHistoryItem;
174+
175+
return (
176+
<Fragment key={index}>
177+
<div className="vrcbChatMessageContainer vrcbChatMessageContainer--actionResponse">
178+
<VuiFlexContainer spacing="m">
179+
{options?.map((option: ChatActionOption, optionIndex: number) => (
180+
<VuiTopicButton
181+
key={`messageHistoryItem-${index}-actionOption-${optionIndex}`}
182+
href={option.url}
183+
title={option.label}
184+
onClick={() => {
185+
if (option.message) {
186+
sendMessage({ query: option.message });
187+
}
188+
option.onSelect?.();
189+
}}
190+
/>
191+
))}
192+
</VuiFlexContainer>
193+
</div>
194+
</Fragment>
195+
);
196+
} else {
197+
const { question, answer, results, factualConsistencyScore } = messageHistoryItem;
198+
const onRetry =
199+
hasError && index === messageHistory.length - 1
200+
? () => sendMessage({ query: question, isRetry: true })
201+
: undefined;
202+
203+
return (
204+
<Fragment key={index}>
205+
<ChatItem
206+
question={question}
207+
answer={answer}
208+
searchResults={results}
209+
factualConsistencyScore={
210+
enableFactualConsistencyScore && <FactualConsistencyBadge score={factualConsistencyScore} />
211+
}
212+
onRetry={onRetry}
213+
/>
214+
{index < messageHistory.length - 1 && <VuiSpacer size="m" />}
215+
</Fragment>
216+
);
217+
}
174218
}),
175219
[messageHistory]
176220
);
177221

178222
const hasContent = isLoading || messageHistory.length > 0 || activeMessage;
179223
const isRequestDisabled = isLoading || isStreamingResponse || query.trim().length === 0;
180224

181-
const onSendQuery = (queryOverride?: string) => {
225+
const onSendQuery = async (queryOverride?: string) => {
182226
if (isRequestDisabled && !queryOverride) return;
183-
sendMessage({ query: queryOverride ?? query });
227+
184228
setQuery("");
229+
230+
sendMessage({ query: queryOverride ?? query });
185231
};
186232

187233
const spacer = historyItems.length === 0 ? null : <VuiSpacer size={activeMessage ? "m" : "l"} />;

src/components/chatView.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ $chatbotPosition: $sizeS;
4949
}
5050
}
5151

52+
.vrcbChatMessageContainer--actionResponse {
53+
justify-content: flex-start;
54+
padding: $sizeM $sizeXxl $sizeM $sizeS;
55+
56+
.vrcbChatMessage {
57+
color: $colorText;
58+
font-weight: $fontWeightBold;
59+
font-size: $fontSizeStandard;
60+
padding-left: 0;
61+
}
62+
}
63+
5264
.vrcbChatMessageContainer--thinking,
5365
.vrcbChatMessageContainer--answer {
5466
padding: 0 $sizeXxl;

src/index.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ReactNode, useEffect, useRef } from "react";
22
import * as ReactDOM from "react-dom";
33
import { Props, ChatView } from "./components/ChatView";
4-
import type {RerankerId, SummaryLanguage} from "./types";
4+
import type { AgenticConfiguration, RerankerId, SummaryLanguage } from "./types";
55
export type { Props } from "components/ChatView";
66
export { DEFAULT_SUMMARIZER, DEFAULT_RERANKER_ID, DEFAULT_LAMBDA_VALUE } from "./useChat";
77

@@ -15,6 +15,7 @@ class ReactChatbotWebComponent extends HTMLElement {
1515

1616
// References
1717
emptyStateDisplay!: ReactNode;
18+
agenticConfiguration!: AgenticConfiguration;
1819

1920
static get observedAttributes() {
2021
return [
@@ -35,6 +36,8 @@ class ReactChatbotWebComponent extends HTMLElement {
3536
"rerankerId",
3637
"lambda",
3738
"enablestreaming",
39+
"agenticconfigurationupdatetime",
40+
"requestsource"
3841
];
3942
}
4043

@@ -67,9 +70,15 @@ class ReactChatbotWebComponent extends HTMLElement {
6770
this.setAttribute("emptystatedisplayupdatetime", Date.now().toString());
6871
}
6972

73+
public setAgenticConfiguration(agenticConfiguration: AgenticConfiguration) {
74+
this.agenticConfiguration = agenticConfiguration;
75+
76+
this.setAttribute("agenticconfigurationupdatetime", Date.now().toString());
77+
}
78+
7079
public connectedCallback() {
7180
const customerId = this.getAttribute("customerId") ?? "";
72-
const corpusKeys = (this.getAttribute("corpuskeys") ?? "");
81+
const corpusKeys = this.getAttribute("corpuskeys") ?? "";
7382
const apiKey = this.getAttribute("apiKey") ?? "";
7483
const title = this.getAttribute("title") ?? undefined;
7584
const placeholder = this.getAttribute("placeholder") ?? undefined;
@@ -83,12 +92,14 @@ class ReactChatbotWebComponent extends HTMLElement {
8392
const language = (this.getAttribute("language") as SummaryLanguage) ?? undefined;
8493
const enableFactualConsistencyScore = this.getAttribute("enableFactualConsistencyScore") === "true";
8594
const summaryPromptName = this.getAttribute("summaryPromptName") ?? undefined;
86-
const rerankerId = this.getAttribute("rerankerId") !== null ? parseInt(this.getAttribute("rerankerId")!, 10) : undefined;
95+
const rerankerId =
96+
this.getAttribute("rerankerId") !== null ? parseInt(this.getAttribute("rerankerId")!, 10) : undefined;
8797
const lambda = this.getAttribute("lambda") !== null ? parseFloat(this.getAttribute("lambda")!) : undefined;
8898
const enableStreaming =
89-
this.getAttribute("enableStreaming") !== null ? this.getAttribute("enableStreaming") == "true" : undefined;
99+
this.getAttribute("enableStreaming") !== null ? this.getAttribute("enableStreaming") == "true" : undefined;
100+
const agenticConfiguration = this.agenticConfiguration ?? undefined;
101+
const requestSource = this.getAttribute("requestsource") ?? undefined;
90102

91-
console.log(corpusKeys)
92103
ReactDOM.render(
93104
<div>
94105
<ChatView
@@ -109,6 +120,8 @@ class ReactChatbotWebComponent extends HTMLElement {
109120
summaryPromptName={summaryPromptName}
110121
rerankerId={rerankerId as RerankerId}
111122
lambda={lambda}
123+
agenticConfiguration={agenticConfiguration}
124+
requestSource={requestSource}
112125
/>
113126
</div>,
114127
this.mountPoint
@@ -134,6 +147,11 @@ export const ReactChatbot = (props: Props) => {
134147
// @ts-ignore
135148
(ref.current as ReactChatbotWebComponent).setEmptyStateDisplay(props.emptyStateDisplay);
136149
}
150+
151+
if (props.agenticConfiguration) {
152+
// @ts-ignore
153+
(ref.current as ReactChatbotWebComponent).setAgenticConfiguration(props.agenticConfiguration);
154+
}
137155
}, [props]);
138156

139157
const typedProps = props as Record<string, any>;

0 commit comments

Comments
 (0)