fix(): build out opendevin modal component (#1141)

This commit is contained in:
Akki 2024-04-16 16:43:04 +04:00 committed by GitHub
parent 516c9bf1e0
commit 7e825b571f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 247 additions and 149 deletions

View File

@ -1,77 +1,82 @@
import React from "react";
import {
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Button,
} from "@nextui-org/react";
import { Button } from "@nextui-org/react";
import { fetchMsgs, clearMsgs } from "../services/session";
import { sendChatMessageFromEvent } from "../services/chatService";
import { handleAssistantMessage } from "../services/actions";
import { ResFetchMsg } from "../types/ResponseType";
import ODModal from "./ODModal";
import toast from "../utils/toast";
interface Props {
interface LoadMessageModalProps {
isOpen: boolean;
onClose: () => void;
}
function LoadMessageModal({ isOpen, onClose }: Props): JSX.Element {
const handleDelMsg = () => {
function LoadMessageModal({
isOpen,
onClose,
}: LoadMessageModalProps): JSX.Element {
const handleStartNewSession = () => {
clearMsgs().then().catch();
onClose();
};
const handleLoadMsg = () => {
fetchMsgs()
.then((data) => {
if (
data === undefined ||
data.messages === undefined ||
data.messages.length === 0
) {
return;
const handleResumeSession = async () => {
try {
const data = await fetchMsgs();
if (!data || !data.messages || data.messages.length === 0) {
return;
}
data.messages.forEach((msg: ResFetchMsg) => {
switch (msg.role) {
case "user":
sendChatMessageFromEvent(msg.payload);
break;
case "assistant":
handleAssistantMessage(msg.payload);
break;
default:
break;
}
const { messages } = data;
messages.forEach((msg: ResFetchMsg) => {
switch (msg.role) {
case "user":
sendChatMessageFromEvent(msg.payload);
break;
case "assistant":
handleAssistantMessage(msg.payload);
break;
default:
}
});
})
.catch();
onClose();
});
onClose();
} catch (error) {
toast.stickyError("ws", "Error fetching the session");
}
};
return (
<Modal isOpen={isOpen} onClose={onClose} hideCloseButton backdrop="blur">
<ModalContent>
<>
<ModalHeader className="flex flex-col gap-1">
Unfinished Session Detected
</ModalHeader>
<ModalBody>
You have an unfinished session. Do you want to load it?
</ModalBody>
<ModalFooter>
<Button color="danger" variant="light" onPress={handleDelMsg}>
No, start a new session
</Button>
<Button color="primary" onPress={handleLoadMsg}>
Okay, load it
</Button>
</ModalFooter>
</>
</ModalContent>
</Modal>
<ODModal
size="md"
isOpen={isOpen}
onClose={onClose}
hideCloseButton
backdrop="blur"
title="Unfinished Session Detected"
primaryAction={
<Button
className="bg-primary rounded-small"
onPress={handleResumeSession}
>
Resume Session
</Button>
}
secondaryAction={
<Button
className="bg-neutral-500 rounded-small"
onPress={handleStartNewSession}
>
Start New Session
</Button>
}
>
<p>
You seem to have an unfinished task. Would you like to pick up where you
left off or start fresh?
</p>
</ODModal>
);
}

View File

@ -0,0 +1,71 @@
import React from "react";
import {
ModalProps,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@nextui-org/react";
interface ODModalProps extends Omit<ModalProps, "children"> {
title?: string;
subtitle?: string;
primaryAction?: React.ReactNode;
secondaryAction?: React.ReactNode;
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
size: "sm" | "md";
}
function ODModal(props: ODModalProps): React.ReactElement {
const {
children,
title,
subtitle,
primaryAction,
secondaryAction,
size,
...modalProps
} = props;
return (
<Modal
className="bg-neutral-900 rounded-large"
// eslint-disable-next-line react/jsx-props-no-spreading
{...modalProps}
>
<ModalContent
className={`${size === "sm" ? "max-w-[24rem]" : "max-w-[52rem]"} p-[40px]`}
>
<ModalHeader className="flex flex-col p-0">
{title && <h3>{title}</h3>}
{subtitle && (
<span className="text-neutral-400 text-sm font-light">
{subtitle}
</span>
)}
</ModalHeader>
<ModalBody className="px-0 py-[20px]">{children}</ModalBody>
{(primaryAction || secondaryAction) && (
<ModalFooter
className={`${size === "sm" ? "flex-col" : "flex-row"} flex justify-start p-0`}
>
{primaryAction}
{secondaryAction}
</ModalFooter>
)}
</ModalContent>
</Modal>
);
}
ODModal.defaultProps = {
title: "",
subtitle: "",
primaryAction: null,
secondaryAction: null,
};
export default ODModal;

View File

@ -4,11 +4,6 @@ import {
Autocomplete,
AutocompleteItem,
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Select,
SelectItem,
} from "@nextui-org/react";
@ -25,6 +20,7 @@ import { RootState } from "../store";
import { I18nKey } from "../i18n/declaration";
import { AvailableLanguages } from "../i18n";
import { ArgConfigType } from "../types/ConfigType";
import ODModal from "./ODModal";
interface Props {
isOpen: boolean;
@ -88,82 +84,83 @@ function InnerSettingModal({ isOpen, onClose }: Props): JSX.Element {
item.toLowerCase().includes(input.toLowerCase());
return (
<Modal isOpen={isOpen} onClose={onClose} hideCloseButton backdrop="blur">
<ModalContent>
<>
<ModalHeader className="flex flex-col gap-1">
{t(I18nKey.CONFIGURATION$MODAL_TITLE)}
</ModalHeader>
<ModalBody>
<Autocomplete
defaultItems={supportedModels.map((v: string) => ({
label: v,
value: v,
}))}
label={t(I18nKey.CONFIGURATION$MODEL_SELECT_LABEL)}
placeholder={t(I18nKey.CONFIGURATION$MODEL_SELECT_PLACEHOLDER)}
selectedKey={model}
onSelectionChange={(key) => {
setModel(key as string);
}}
onInputChange={(e) => setInputModel(e)}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
defaultInputValue={inputModel}
allowsCustomValue
>
{(item: { label: string; value: string }) => (
<AutocompleteItem key={item.value} value={item.value}>
{item.label}
</AutocompleteItem>
)}
</Autocomplete>
<Autocomplete
defaultItems={supportedAgents.map((v: string) => ({
label: v,
value: v,
}))}
label={t(I18nKey.CONFIGURATION$AGENT_SELECT_LABEL)}
placeholder={t(I18nKey.CONFIGURATION$AGENT_SELECT_PLACEHOLDER)}
defaultSelectedKey={agent}
onSelectionChange={(key) => {
setAgent(key as string);
}}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
>
{(item: { label: string; value: string }) => (
<AutocompleteItem key={item.value} value={item.value}>
{item.label}
</AutocompleteItem>
)}
</Autocomplete>
<Select
selectionMode="single"
onChange={(e) => setLanguage(e.target.value)}
selectedKeys={[language]}
label={t(I18nKey.CONFIGURATION$LANGUAGE_SELECT_LABEL)}
>
{AvailableLanguages.map((lang) => (
<SelectItem key={lang.value} value={lang.value}>
{lang.label}
</SelectItem>
))}
</Select>
</ModalBody>
<ModalFooter>
<Button color="danger" variant="light" onPress={onClose}>
{t(I18nKey.CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL)}
</Button>
<Button color="primary" onPress={handleSaveCfg}>
{t(I18nKey.CONFIGURATION$MODAL_SAVE_BUTTON_LABEL)}
</Button>
</ModalFooter>
</>
</ModalContent>
</Modal>
<ODModal
isOpen={isOpen}
onClose={onClose}
title={t(I18nKey.CONFIGURATION$MODAL_TITLE)}
subtitle={t(I18nKey.CONFIGURATION$MODAL_SUB_TITLE)}
hideCloseButton
backdrop="blur"
size="sm"
primaryAction={
<Button className="bg-primary rounded-small" onPress={handleSaveCfg}>
{t(I18nKey.CONFIGURATION$MODAL_SAVE_BUTTON_LABEL)}
</Button>
}
secondaryAction={
<Button className="bg-neutral-500 rounded-small" onPress={onClose}>
{t(I18nKey.CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL)}
</Button>
}
>
<>
<Autocomplete
defaultItems={supportedModels.map((v: string) => ({
label: v,
value: v,
}))}
label={t(I18nKey.CONFIGURATION$MODEL_SELECT_LABEL)}
placeholder={t(I18nKey.CONFIGURATION$MODEL_SELECT_PLACEHOLDER)}
selectedKey={model}
onSelectionChange={(key) => {
setModel(key as string);
}}
onInputChange={(e) => setInputModel(e)}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
defaultInputValue={inputModel}
allowsCustomValue
>
{(item: { label: string; value: string }) => (
<AutocompleteItem key={item.value} value={item.value}>
{item.label}
</AutocompleteItem>
)}
</Autocomplete>
<Autocomplete
defaultItems={supportedAgents.map((v: string) => ({
label: v,
value: v,
}))}
label={t(I18nKey.CONFIGURATION$AGENT_SELECT_LABEL)}
placeholder={t(I18nKey.CONFIGURATION$AGENT_SELECT_PLACEHOLDER)}
defaultSelectedKey={agent}
onSelectionChange={(key) => {
setAgent(key as string);
}}
onKeyDown={(e: KeyboardEvent) => e.continuePropagation()}
defaultFilter={customFilter}
>
{(item: { label: string; value: string }) => (
<AutocompleteItem key={item.value} value={item.value}>
{item.label}
</AutocompleteItem>
)}
</Autocomplete>
<Select
selectionMode="single"
onChange={(e) => setLanguage(e.target.value)}
selectedKeys={[language]}
label={t(I18nKey.CONFIGURATION$LANGUAGE_SELECT_LABEL)}
>
{AvailableLanguages.map((lang) => (
<SelectItem key={lang.value} value={lang.value}>
{lang.label}
</SelectItem>
))}
</Select>
</>
</ODModal>
);
}

View File

@ -95,6 +95,18 @@
"es": "Configuración",
"tr": "Konfigürasyon"
},
"CONFIGURATION$MODAL_SUB_TITLE": {
"en": "Adjust settings to your liking",
"zh-CN": "根据您的喜好调整设置",
"de": "Passen Sie die Einstellungen nach Ihren Wünschen an ",
"ko-KR": "원하는 대로 설정 조정",
"no": "Juster innstillinger etter dine ønsker ",
"zh-TW": "調整設定以符合您的喜好",
"it": "Regola le impostazioni in base alle tue preferenze",
"pt": "Ajuste as configurações de acordo com sua preferência",
"es": "Ajusta la configuración a tu gusto",
"tr": "Ayarları isteğinize göre ayarlayın"
},
"CONFIGURATION$MODEL_SELECT_LABEL": {
"en": "Model",
"zh-CN": "模型",
@ -201,7 +213,7 @@
"it": "Invia un messaggio (non interromperà l'Assistente)",
"pt": "Envie uma mensagem (não interromperá o Assistente)",
"es": "Enviar un mensaje (no interrumpirá al Asistente)",
"tr": "Bir mesaj gönderin (Asistan Kesilmeyecek)"
"tr": "Bir mesaj gönderin (Asistan Kesilmeyecek)"
},
"CHAT_INTERFACE$INPUT_SEND_MESSAGE_BUTTON_CONTENT": {
"en": "Send",
@ -215,4 +227,4 @@
"es": "Enviar",
"tr": "Gönder"
}
}
}

View File

@ -1,14 +1,27 @@
/** @type {import('tailwindcss').Config} */
const { nextui } = require("@nextui-org/react");
export default {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
plugins: [
nextui({
defaultTheme: "dark",
}),
],
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
plugins: [
nextui({
defaultTheme: "dark",
layout: {
radius: {
small: "5px",
large: "20px",
},
},
themes: {
dark: {
colors: {
primary:"#4465DB",
},
}
}
}),
],
};