Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"near-api-js": "^2.1.4",
"next": "12.2.0",
"next-i18next": "^8.2.0",
"openai": "^4.3.1",
"openai": "^4.28.4",
"react": "^18.2.0",
"react-countdown": "^2.3.5",
"react-dom": "^18.2.0",
Expand Down
42 changes: 30 additions & 12 deletions app/src/app/chat/dropbox-chat/DropboxChat.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,39 @@
@mixin width {
@include atLargeTablet {
max-width: 50vw;
margin: 0 auto;
}
}

.dropbox-chat {
position: relative;
display: block;
min-height: 100vh;
height: 100vh;
padding-top: $navbar-height * 1.5;
padding-left: $navbar-height * 1.5;
background-color: var(--color-background);

&__textarea {
@include atLargeTablet {
padding: $space-l;
padding-top: 0;
padding-left: $navbar-height * 1.5;
}
position: fixed;
right: 0;
bottom: 0;
left: 0;
width: 100%;
border-top: 1px solid var(--color-horizontal-line-background);
padding: $space-default;
padding-top: 0;
background-color: var(--color-background);

> div {
@extend .z-depth-1;
padding: $space-default;
padding-bottom: $space-l;
padding-left: $navbar-height;
background-color: white;
}

&--actions {
@include width;
display: flex;
Expand All @@ -42,6 +51,11 @@

&--card {
@include width;
background-color: var(--color-background);
}

&--card-content {
display: flex;
}

&--card-actions {
Expand All @@ -55,6 +69,9 @@
@include atLargeTablet {
margin-left: $space-default;
}
display: flex;
flex-direction: column;
justify-content: flex-end;
margin-left: auto;
}
}
Expand All @@ -63,33 +80,34 @@
min-height: 63px;
padding: $space-default !important;
font-size: $input-font-size !important;
background-color: var(--color-market-card-background);
background-color: var(--color-input-background);
transition: height 0.2s ease-in-out;
}
}

&__messages {
@include atLargeTablet {
padding-top: $navbar-height;
padding-bottom: 270px;
}
padding-top: $navbar-height-mobile;

@extend .z-depth-1;
position: relative;
height: calc(100vh - $navbar-height * 1.5);
border-top-left-radius: $border-radius-card;
padding-top: $navbar-height;
padding-bottom: 280px;
overflow-y: scroll;
background-color: white;

&--item {
border-bottom: 1px solid var(--color-horizontal-line-background);
padding-left: $navbar-height;

> div {
@include width;
display: flex;
padding-top: $space-m;
}

&:nth-child(odd) {
background-color: var(--color-background-contrast);
}

&:last-child {
border-bottom: none;
}
Expand Down
74 changes: 43 additions & 31 deletions app/src/app/chat/dropbox-chat/DropboxChat.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import clsx from "clsx";
import { Field, useForm } from "react-final-form";
import { useEffect } from "react";
import { useRouter } from "next/router";

import { Card } from "ui/card/Card";
import { Button } from "ui/button/Button";
import { Dropzone } from "ui/dropzone/Dropzone";
import { useMessageContext } from "context/message/useMessageContext";
import { ChatContextMessage } from "context/message/MessageContext.types";
import { MessageFileType } from "ui/dropzone/message-file-type/MessageFileType";
Expand All @@ -19,6 +19,8 @@ export const DropboxChat: React.FC<DropboxChatProps> = ({ className, onSubmit })

const formContext = useFormContext();

const router = useRouter();

const { messages, actions, clearMessages, saveMessageThread } = useMessageContext();

useEffect(() => {
Expand All @@ -27,6 +29,19 @@ export const DropboxChat: React.FC<DropboxChatProps> = ({ className, onSubmit })
formContext.setForm(form);
}, [form]);

useEffect(() => {
if (!formContext.form) return;

const fileName = router.query?.fileName;

if (fileName) {
formContext.setFieldValue(
FormFieldNames.message,
`Extra el contenido de "${fileName}" y responde: ¿cuántos artículos tiene la Constitución Política de Guatemala?`,
);
}
}, [formContext.form]);

useEffect(() => {
const element = document.querySelector(`#messages`);

Expand Down Expand Up @@ -74,38 +89,35 @@ export const DropboxChat: React.FC<DropboxChatProps> = ({ className, onSubmit })
<div className={styles["dropbox-chat__messages"]} id="messages">
{messages.map((message) => getMessageTypeComponent(message))}
</div>

<div className={styles["dropbox-chat__textarea"]}>
<div className={styles["dropbox-chat__textarea--actions"]}>
<Button variant="text" color="secondary" size="s" onClick={clearMessages}>
Clear
</Button>
<Button variant="text" color="secondary" size="s" onClick={saveMessageThread}>
Save this thread
</Button>
<div>
<div className={styles["dropbox-chat__textarea--actions"]}>
<Button variant="text" color="secondary" size="s" onClick={clearMessages}>
Clear
</Button>
<Button variant="text" color="secondary" size="s" onClick={saveMessageThread}>
Save this thread
</Button>
</div>
<Card className={styles["dropbox-chat__textarea--card"]} shadow>
<Card.Content className={styles["dropbox-chat__textarea--card-content"]}>
<Field
name={FormFieldNames.message}
component="textarea"
className={clsx(styles["dropbox-chat__textarea--card-field"], "input-field", "materialize-textarea")}
id="message"
onKeyDown={onKeyDown}
placeholder="Type your message here..."
disabled={actions.isProcessingRequest}
/>
<div className={styles["dropbox-chat__textarea--card-actions-button"]}>
<Button type="submit" disabled={actions.isProcessingRequest}>
Send
</Button>
</div>
</Card.Content>
</Card>
</div>
<Card className={styles["dropbox-chat__textarea--card"]} shadow>
<Card.Content>
<Field
name={FormFieldNames.message}
component="textarea"
className={clsx(styles["dropbox-chat__textarea--card-field"], "input-field", "materialize-textarea")}
id="message"
onKeyDown={onKeyDown}
placeholder="Type your message here..."
disabled={actions.isProcessingRequest}
/>
</Card.Content>
<Card.Actions className={styles["dropbox-chat__textarea--card-actions"]}>
<Dropzone />

<div className={styles["dropbox-chat__textarea--card-actions-button"]}>
<Button type="submit" disabled={actions.isProcessingRequest}>
Send
</Button>
</div>
</Card.Actions>
</Card>
</div>
</form>
);
Expand Down
4 changes: 4 additions & 0 deletions app/src/app/chat/dropbox-chat/DropboxChatContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const DropboxChatContainer = () => {
}, []);

const onSubmit = async (values: ChatFormValues) => {
if (!values.message) {
return;
}

formContext.submit(values);
};

Expand Down
2 changes: 2 additions & 0 deletions app/src/context/authorization/AuthorizationContext.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export type AuthorizationContextType = {
authItems: Array<AuthItem>;
revokeAuth: (key: OAuthTokenStoreKey) => void;
getGuestId: () => string | null;
getOpenAISessionID: () => string | undefined;
generateGuestId: () => string;
setOpenAISessionID: (threadId: string) => void;
verifyDropboxESignAuthorization: () => Promise<void>;
verifySquareAPIAuthorization: () => Promise<void>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const AuthorizationContextController = ({ children }: AuthorizationContex

const ls = useLocalStorage();

const setOpenAISessionID = (threadId: string) => {
ls.set(LocalStorageKeys.openAISessionID, threadId);
};

const generateGuestId = () => {
const id = `guest-${uuidv4().slice(0, 4)}`;

Expand All @@ -32,6 +36,7 @@ export const AuthorizationContextController = ({ children }: AuthorizationContex
};

const getGuestId = () => ls.get<string>(LocalStorageKeys.guestId);
const getOpenAISessionID = () => ls.get<string>(LocalStorageKeys.openAISessionID) || undefined;

const revokeAuth = (key: OAuthTokenStoreKey) => {
Cookies.remove(key);
Expand Down Expand Up @@ -98,9 +103,11 @@ export const AuthorizationContextController = ({ children }: AuthorizationContex
verifySquareAPIAuthorization,
accessTokens,
getGuestId,
getOpenAISessionID,
generateGuestId,
authItems,
revokeAuth,
setOpenAISessionID,
};

return <AuthorizationContext.Provider value={props}>{children}</AuthorizationContext.Provider>;
Expand Down
1 change: 1 addition & 0 deletions app/src/context/form/FormContext.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type FormContextControllerProps = {
};

export type FormContextType = {
form?: FormState;
setForm: Dispatch<SetStateAction<FormState | undefined>>;
setCurrentMessageMetadata: Dispatch<SetStateAction<Record<string, any>>>;
setFieldValue: (field: string, text: string) => void;
Expand Down
31 changes: 29 additions & 2 deletions app/src/context/form/FormContextController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import { sample } from "lodash";
import { APIChatHeaderKeyNames, CurrentMessageMetadata, FileAgentRequest, FileAgentResponse } from "api/chat/types";
import { OAuthTokenStoreKey } from "api/oauth/oauth.types";
import axios from "axios";
import { useRouter } from "next/router";

import { useMessageContext } from "context/message/useMessageContext";
import { ChatFormValues, FormFieldNames } from "app/chat/dropbox-chat/DropboxChat.types";
import { ChatContextMessage, TextChatCompletionMessage } from "context/message/MessageContext.types";
import {
ChatContextMessage,
OpenAIAssistantMetadata,
TextChatCompletionMessage,
} from "context/message/MessageContext.types";
import { useRoutes } from "hooks/useRoutes/useRoutes";
import { useAuthorizationContext } from "context/authorization/useAuthorizationContext";
import { useFileContext } from "context/file/useFileContext";
import { X_PUBLIC_BUCKET_NAME } from "providers/chat/constants";

import { FormContextControllerProps, FormContextType, FormState } from "./FormContext.types";
import { FormContext } from "./FormContext";
Expand Down Expand Up @@ -56,10 +62,18 @@ export const FormContextController = ({ children }: FormContextControllerProps)

const fileContext = useFileContext();

const router = useRouter();

useEffect(() => {
setCurrentMessageMetadata({ bucketName: fileContext.getStorageBucketName() });
}, []);

useEffect(() => {
if (authContext.getOpenAISessionID()) {
setCurrentMessageMetadata((prev) => ({ ...prev, openai: { threadId: authContext.getOpenAISessionID() } }));
}
}, [messageContext.messages]);

const setFieldValue = (field: string, text: string) => {
form?.mutators.setValue(field, text);

Expand Down Expand Up @@ -115,6 +129,12 @@ export const FormContextController = ({ children }: FormContextControllerProps)
headers[APIChatHeaderKeyNames.x_square_access_token] = authContext.accessTokens[OAuthTokenStoreKey.square_api]!;
}

const fileName = router.query?.fileName;

if (fileName) {
headers[APIChatHeaderKeyNames.x_public_bucket_name] = X_PUBLIC_BUCKET_NAME;
}

const options = {
method: "POST",
body: JSON.stringify({
Expand All @@ -127,14 +147,20 @@ export const FormContextController = ({ children }: FormContextControllerProps)
};

const result = await (process.env.NEXT_PUBLIC_CHAT_AI_API === "openai"
? axios.post<FileAgentResponse>(routes.api.chat.openai.completionsAPI(), options)
? axios.post<FileAgentResponse>(routes.api.chat.openai.assistantsAPI(), options)
: axios.post<FileAgentResponse>(routes.api.chat.googleai.completionsAPI(), options));

console.log(result);

messageContext.deleteMessage(loadingMessage.id!);

messageContext.appendMessage({ ...result.data.choices[0].message } as TextChatCompletionMessage);

const openAIThreadID = (result.data.choices[0].message.metadata as OpenAIAssistantMetadata)?.openai?.threadId;

if (openAIThreadID) {
authContext.setOpenAISessionID(openAIThreadID!);
}
} catch (error) {
console.log(error);

Expand Down Expand Up @@ -166,6 +192,7 @@ export const FormContextController = ({ children }: FormContextControllerProps)
updateTextareaHeight,
resetTextareaHeight,
submit,
form,
};

return <FormContext.Provider value={props}>{children}</FormContext.Provider>;
Expand Down
13 changes: 10 additions & 3 deletions app/src/context/message/MessageContext.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChatCompletionMessage } from "openai/resources/chat";
import { ChatCompletionMessageParam } from "openai/resources/chat";
import { Dispatch, ReactNode, SetStateAction } from "react";

import { SquareGetLocationsMetadata } from "providers/chat/functions/square/square.types";
Expand Down Expand Up @@ -27,15 +27,22 @@ export enum ChatLabel {
chat_completion_error = "chat:completion:error",
}

export type ChatMessageBase = ChatCompletionMessage & {
export type OpenAIAssistantMetadata = {
openai?: {
threadId?: string;
};
};

export type ChatMessageBase = ChatCompletionMessageParam & {
id?: string;
beforeContentComponent?: ReactNode;
afterContentComponent?: ReactNode;
hasInnerHtml?: boolean; // Setting this to true will render text with Markdown parsing and or HTML parsing such as anchor elements
readOnly?: boolean;
type?: "text" | "file";
label?: DropboxESignLabel | ChatLabel | SquareAPILabel;
metadata?: SquareGetLocationsMetadata;
metadata?: SquareGetLocationsMetadata | OpenAIAssistantMetadata;
role: "user" | "assistant";
};

export type TextChatCompletionMessage = {
Expand Down
Loading