Skip to content

Commit

Permalink
Handle uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
peterszerzo committed Oct 25, 2024
1 parent d4c3848 commit 029b596
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 5 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions packages/chat-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ export interface BotResponseMetadata {
* Whether the current conversation has been marked as incomprehension.
*/
incomprehension?: boolean;
/**
* Upload URL's
*/
uploadUrls: UploadUrl[];
/**
* Whether the client should poll for more bot responses.
*/
Expand Down Expand Up @@ -176,6 +180,20 @@ export interface BotMessage {
selectedChoiceId?: string;
}

/**
* The upload destination for handling conversing with files
*/
export interface UploadUrl {
/**
* The URL of the upload
*/
url: string;
/**
* The ID of the upload
*/
uploadId: string;
}

/**
* A choices to show to the user.
*/
Expand Down Expand Up @@ -401,6 +419,14 @@ export interface StructuredRequest {
* The slots to populate
*/
slots?: SlotsRecordOrArray;
/**
* Upload ID
*/
uploadId?: string;
/**
* Upload utterance
*/
utterance?: string;
/**
* @hidden
* This is used internally to indicate that the client is polling the bot for more data.
Expand Down
10 changes: 9 additions & 1 deletion packages/chat-widget/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ export const ChatIcon = (): ReactNode => (
// eslint-disable-next-line jsdoc/require-returns
/** @hidden @internal */
export const SendIcon = (): ReactNode => (
<svg viewBox="0 0 360 360" stroke="none" fill="currentColor">
<svg viewBox="0 0 24 24" stroke="none" fill="currentColor">
<path d="M2.01 21 23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
);

// eslint-disable-next-line jsdoc/require-returns
/** @hidden @internal */
export const AddPhotoIcon = (): ReactNode => (
<svg viewBox="0 0 24 24" stroke="none" fill="currentColor">
<path d="M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2zm-3 4V8h-3V5H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8zM5 19l3-4 2 3 3-4 4 5z" />
</svg>
);

// eslint-disable-next-line jsdoc/require-returns
/** @hidden @internal */
export const DownloadIcon = (): ReactNode => (
Expand Down
24 changes: 23 additions & 1 deletion packages/chat-widget/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ import { ThemeProvider } from "@emotion/react";
import { useChat, type ChatHook } from "@nlxai/chat-react";
import {
type Response,
type BotResponse,
type ConversationHandler,
type UploadUrl,
getCurrentExpirationTimestamp,
} from "@nlxai/chat-core";
import {
CloseIcon,
MinimizeIcon,
ChatIcon,
SendIcon,
AddPhotoIcon,
ErrorOutlineIcon,
} from "./icons";
import { last, equals } from "ramda";
import { last, equals, findLast } from "ramda";
import * as constants from "./ui/constants";
import {
type Props,
Expand Down Expand Up @@ -352,6 +355,17 @@ const isInputDisabled = (responses: Response[]): boolean => {
return new URLSearchParams(payload).get("nlx:input-disabled") === "true";
};

const findActiveUpload = (responses: Response[]): UploadUrl | null => {
const lastBotResponse = findLast(
(response): response is BotResponse => response.type === "bot",
responses,
);
if (lastBotResponse == null) {
return null;
}
return lastBotResponse.payload.metadata?.uploadUrls?.[0] ?? null;
};

/**
* Hook to get the ConversationHandler for the widget.
* This may be called before the Widget has been created.
Expand Down Expand Up @@ -565,6 +579,8 @@ export const Widget = forwardRef<WidgetRef, Props>(function Widget(props, ref) {
[props.theme, windowInnerHeightValue, inputDisabled],
);

const activeUpload = findActiveUpload(chat.responses);

return (
<ConversationHandlerContext.Provider value={chat.conversationHandler}>
<ThemeProvider theme={mergedTheme}>
Expand Down Expand Up @@ -652,6 +668,12 @@ export const Widget = forwardRef<WidgetRef, Props>(function Widget(props, ref) {
}}
/>
<C.BottomButtonsContainer>
{activeUpload == null ? null : (
<C.UploadIconLabel>
<input type="file" />
<AddPhotoIcon />
</C.UploadIconLabel>
)}
<C.IconButton
disabled={Boolean(submit === false)}
onClick={() => {
Expand Down
56 changes: 56 additions & 0 deletions packages/chat-widget/src/ui/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,59 @@ export const IconButton = styled.button<{
${hoverBg}
`;

export const UploadIconLabel = styled.label<{
/** @hidden @internal */
disabled?: boolean;
}>`
height: 35px;
width: 35px;
border-radius: 18px;
flex: none;
padding: 8px;
font-size: ${constants.fontSize}px;
${(props) =>
props.disabled === true
? `
opacity: 0.6;
`
: `
`}
border: 0;
box-shadow: none;
color: ${(props) => props.theme.primaryColor};
background: none;
position: relative;
cursor: pointer;
:focus {
outline: none;
${(props) => focusShadow(props.theme)}
}
:disabled {
cursor: auto;
}
svg {
fill: ${(props) => props.theme.primaryColor};
}
input {
/** Screen-reader-only implementation */
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
${hoverBg}
`;

export const BottomButtonsContainer = styled.div<{
/**
*
Expand All @@ -368,6 +421,8 @@ export const BottomButtonsContainer = styled.div<{
top: 50%;
right: ${(props) => `${props.theme.spacing}px`};
transform: translate3d(0, -50%, 0);
display: flex;
align-items: center;
`;

export const Input = styled.input<{
Expand Down Expand Up @@ -463,6 +518,7 @@ interface PinBubbleProps {
*/
onClick: () => void;
}

// eslint-disable-next-line jsdoc/require-returns, jsdoc/require-param
/** @hidden @internal */
export const PinBubble: React.FunctionComponent<PinBubbleProps> = (
Expand Down

0 comments on commit 029b596

Please sign in to comment.