- refactored chatContainer to handle both promptInput and dyanamicInput

- added toggle button between text promptInput and dynamicInput
- added options sellect with 3 redering types, List, dropdown, buttons
- styled OptionSelect component to match anythingllm buttons theme
This commit is contained in:
sherifButt 2024-03-09 14:46:11 +00:00
parent 5088d8471a
commit 6570ed0e57
5 changed files with 211 additions and 16 deletions

View File

@ -0,0 +1,79 @@
const OptionSelect = ({ data, settings }) => {
const handleSelection = (value) => {
// Implement your logic to handle selection
console.log(value);
};
// Grid of Buttons
if (settings.displayType === "buttons") {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-white/60 text-xs mt-10 w-full justify-center">
{data.options.map((option, index) => (
<button
key={index}
className="text-left p-2.5 border rounded-xl border-white/20 bg-sidebar hover:bg-workspace-item-selected-gradient"
onClick={() => handleSelection(option.value)}
>
<p className="font-semibold">{option.label}</p>
</button>
))}
</div>
);
}
// Normal List with Hyperlinks
if (settings.displayType === "list") {
return (
<div className="mt-10 text-white/60 text-xs w-full">
{data.options.map((option, index) => (
<a
key={index}
href={option.href} // assuming `href` is available in your option object
className="block p-2.5 border-b border-white/20 last:border-0 hover:bg-workspace-item-selected-gradient"
>
<p className="font-semibold">{option.label}</p>
</a>
))}
</div>
);
}
// Dropdown Menu
return (
<div className="mt-5 w-full">
<div className="flex flex-col">
<label
htmlFor="chatModel"
className="block input-label text-white text-opacity-60 text-xs font-medium py-1.5"
>
{data?.label}
</label>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
{data?.description}
</p>
</div>
<select
name="optionSelect"
id="optionSelect"
multiple={settings.allowMultiple}
required={true}
disabled={settings.disabled}
className="bg-sidebar text-white text-xs rounded-xl p-2.5 w-full border border-white/20 focus:ring-blue-500 focus:border-blue-500"
>
{settings.waitingForModels ? (
<option disabled={true} selected={true}>
-- waiting for models --
</option>
) : (
data.options.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))
)}
</select>
</div>
);
};
export default OptionSelect;

View File

@ -0,0 +1,63 @@
import React from "react";
// import TextInput from './TextInput';
import OptionSelect from "@/components/WorkspaceChat/ChatContainer/DynamicInput/OptionSelect";
import { ArrowUUpLeft, Keyboard } from "@phosphor-icons/react";
// import RangeSlider from './RangeSlider';
// import DatePicker from './DatePicker';
// import TimePicker from './TimePicker';
// import DateTimePicker from './DateTimePicker';
// import FileUpload from './FileUpload';
// import Rating from './Rating';
// import Checkbox from './Checkbox';
const inputComponents = {
// text: TextInput,
options: OptionSelect,
// range: RangeSlider,
// date: DatePicker,
// time: TimePicker,
// datetime: DateTimePicker,
// file: FileUpload,
// rating: Rating,
// checkbox: Checkbox
};
const DynamicInput = ({
inputs,
isDynamicInput,
isForcedTextInput,
setIsForcedTextInput,
}) => {
const InputComponent = inputComponents[inputs?.type] || null;
if (!InputComponent) {
return null; // or any fallback UI
}
return (
<div className="w-full fixed md:absolute bottom-10 left-0 z-10 md:z-0 flex justify-center items-center">
<div className="w-[600px]">
<InputComponent {...inputs} />
{isDynamicInput && inputs != undefined && (
<div className="flex justify-end">
<button
type="button"
className="pb-5 transition-all w-fit duration-300 px-5 py-2.5 rounded-lg text-white/50 text-xs items-center flex gap-x-2 hover:text-white focus:ring-gray-800"
onClick={() => setIsForcedTextInput(!isForcedTextInput)}
>
{isForcedTextInput ? (
<>
<ArrowUUpLeft className="h-5 w-5" /> back to options
</>
) : (
<>
<Keyboard className="h-5 w-5" /> Type another answer
</>
)}
</button>
</div>
)}
</div>
</div>
);
};
export default DynamicInput;

View File

@ -52,7 +52,7 @@ export default function PromptInput({
const watchForSlash = debounce(checkForSlash, 300);
return (
<div className="w-full fixed md:absolute bottom-0 left-0 z-10 md:z-0 flex justify-center items-center">
<div className="w-full fixed md:absolute bottom-4 left-0 z-10 md:z-0 flex justify-center items-center">
<SlashCommands
showing={showSlashCommand}
setShowing={setShowSlashCommand}

View File

@ -7,17 +7,24 @@ import { isMobile } from "react-device-detect";
import { SidebarMobileHeader } from "../../Sidebar";
import { useParams } from "react-router-dom";
import { extractMetaData } from "@/utils/chat/extractMetaData";
import DynamicInput from "./DynamicInput";
import { ArrowUUpLeft, Keyboard } from "@phosphor-icons/react";
export default function ChatContainer({ workspace, knownHistory = [], isDynamicInput}) {
export default function ChatContainer({
workspace,
knownHistory = [],
isDynamicInput,
currentInputMeta,
setCurrentInputMeta,
}) {
const { threadSlug = null } = useParams();
const [message, setMessage] = useState("");
const [loadingResponse, setLoadingResponse] = useState(false);
const [chatHistory, setChatHistory] = useState(knownHistory);
const [finalizedChatHistory, setFinalizedChatHistory] =
useState(knownHistory);
const [currentInputMeta, setCurrentInputMeta] = useState(null);
const [isForcedTextInput, setIsForcedTextInput] = useState(false);
const handleMessageChange = (event) => {
setMessage(event.target.value);
};
@ -123,6 +130,31 @@ export default function ChatContainer({ workspace, knownHistory = [], isDynamicI
loadingResponse === true && fetchReply();
}, [loadingResponse, chatHistory, workspace]);
const renderInputComponent = () => {
if (
!isDynamicInput ||
currentInputMeta?.inputs?.type === "text" ||
currentInputMeta?.inputs === undefined ||
isForcedTextInput
) {
return (
<PromptInput
workspace={workspace}
message={message}
submit={handleSubmit}
onChange={handleMessageChange}
inputDisabled={loadingResponse}
buttonDisabled={loadingResponse}
sendCommand={sendCommand}
/>
);
}
if (currentInputMeta?.inputs?.type !== "text") {
return <DynamicInput {...currentInputMeta} />;
}
};
return (
<div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
@ -135,15 +167,26 @@ export default function ChatContainer({ workspace, knownHistory = [], isDynamicI
workspace={workspace}
sendCommand={sendCommand}
/>
<PromptInput
workspace={workspace}
message={message}
submit={handleSubmit}
onChange={handleMessageChange}
inputDisabled={loadingResponse}
buttonDisabled={loadingResponse}
sendCommand={sendCommand}
/>
{renderInputComponent()}
{isDynamicInput && currentInputMeta?.inputs != undefined && (
<div className="w-full fixed md:absolute -bottom-1 left-0 z-10 md:z-0 flex justify-center items-center">
<button
type="button"
className="transition-all w-fit duration-300 px-5 py-2.5 rounded-lg text-white/50 text-xs items-center flex gap-x-2 hover:text-white focus:ring-gray-800"
onClick={() => setIsForcedTextInput(!isForcedTextInput)}
>
{isForcedTextInput ? (
<>
<ArrowUUpLeft className="h-5 w-5" /> back to options
</>
) : (
<>
<Keyboard className="h-5 w-5" /> Type another answer
</>
)}
</button>
</div>
)}
</div>
</div>
);

View File

@ -11,8 +11,9 @@ export default function WorkspaceChat({ loading, workspace }) {
const { threadSlug = null } = useParams();
const [history, setHistory] = useState([]);
const [loadingHistory, setLoadingHistory] = useState(true);
const [currentInputMeta, setCurrentInputMeta] = useState(null);
const isDynamicInput = false;
const isDynamicInput = true;
useEffect(() => {
async function getHistory() {
@ -34,6 +35,7 @@ export default function WorkspaceChat({ loading, workspace }) {
const { remainingText, metaData } = extractMetaData(
message.content
);
setCurrentInputMeta(metaData);
return { ...message, content: remainingText, metaData };
}
return message;
@ -79,7 +81,15 @@ export default function WorkspaceChat({ loading, workspace }) {
}
setEventDelegatorForCodeSnippets();
return <ChatContainer workspace={workspace} knownHistory={history} isDynamicInput={isDynamicInput} />;
return (
<ChatContainer
workspace={workspace}
knownHistory={history}
isDynamicInput={isDynamicInput}
currentInputMeta={currentInputMeta}
setCurrentInputMeta={setCurrentInputMeta}
/>
);
}
// Enables us to safely markdown and sanitize all responses without risk of injection