- toned down chat copy,thumbs up and down icons

- refactored Dynamic Input Component to encapsulate all dynamic input logic, gate and controllers
This commit is contained in:
sherifButt 2024-03-11 06:40:13 +00:00
parent fd1df23ff9
commit 40de9d7bd5
7 changed files with 159 additions and 104 deletions

View File

@ -57,11 +57,11 @@ function FeedbackButton({
onClick={handleFeedback} onClick={handleFeedback}
data-tooltip-id={tooltipId} data-tooltip-id={tooltipId}
data-tooltip-content={tooltipContent} data-tooltip-content={tooltipContent}
className="text-zinc-300" className="text-white/40"
> >
<IconComponent <IconComponent
size={18} size={18}
className="mb-1" className={`mb-1 hover:text-white/80 ${isSelected ? "text-white" : "text-white/40"}`}
weight={isSelected ? "fill" : "regular"} weight={isSelected ? "fill" : "regular"}
/> />
</button> </button>
@ -85,10 +85,10 @@ function CopyMessage({ message }) {
onClick={() => copyText(message)} onClick={() => copyText(message)}
data-tooltip-id="copy-assistant-text" data-tooltip-id="copy-assistant-text"
data-tooltip-content="Copy" data-tooltip-content="Copy"
className="text-zinc-300" className="text-white/40 hover:text-white/80"
> >
{copied ? ( {copied ? (
<Check size={18} className="mb-1" /> <Check size={18} className="mb-1 text-white/80" />
) : ( ) : (
<ClipboardText size={18} className="mb-1" /> <ClipboardText size={18} className="mb-1" />
)} )}
@ -97,7 +97,7 @@ function CopyMessage({ message }) {
id="copy-assistant-text" id="copy-assistant-text"
place="bottom" place="bottom"
delayShow={300} delayShow={300}
className="tooltip !text-xs" className="tooltip !text-xs "
/> />
</div> </div>
</> </>

View File

@ -1,22 +1,42 @@
const OptionSelect = ({ data, settings }) => { import { useEffect, useState } from "react";
const OptionSelect = ({ data, settings, submit, message, setMessage }) => {
const [submitMessage, setSubmitMessage] = useState(false);
useEffect(() => {
if (submitMessage) {
submit();
setSubmitMessage(false);
}
}, [message]);
const handleSelection = (value) => { const handleSelection = (value) => {
// Implement your logic to handle selection setMessage(value);
console.log(value); setSubmitMessage(true);
}; };
// Grid of Buttons // Grid of Buttons
if (settings.displayType === "buttons") { if (settings.displayType === "buttons") {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-white/60 text-xs mt-10 w-full justify-center"> <div className=" mb-2 w-full p-4 backdrop-blur-sm rounded-t-xl overflow-hidden py-4 px-6 border-l border-t border-r border-[#2f3238]">
{data.options.map((option, index) => ( <Label label={data?.label} />
<button <div className=" pb-0 mt-2 grid grid-cols-1 md:grid-cols-2 gap-4 text-white/80 text-xs ">
key={index} {data.options.map((option, index) => (
className="shadow-lg hover:shadow-sm transition-all duration-200 hover:animate-shine ease-in-out text-left p-2.5 border rounded-xl border-white/20 bg-sidebar hover:bg-workspace-item-selected-gradient" <button
onClick={() => handleSelection(option.value)} key={index}
> className="group relative shadow-lg hover:shadow-sm transition-all duration-200 ease-in-out text-left p-2.5 border rounded-xl border-white/20 bg-sidebar hover:bg-sidebar/50 overflow-hidden "
<p className="">{option.label}</p> onClick={() => handleSelection(option.value)}
</button> >
))} <p className="truncate max-w-xl group-hover:max-w-xl group-hover:truncate-0">
<span style={{ color: "grey" }}>{index + 1}.</span>{" "}
{option.label}
</p>
<span className="absolute invisible group-hover:visible bg-black text-white text-xs rounded-lg p-2 left-0 bottom-full mb-2">
<span style={{ color: "grey" }}>{index + 1}.</span>{" "}
{option.label}
</span>
</button>
))}
</div>
</div> </div>
); );
} }
@ -24,14 +44,18 @@ const OptionSelect = ({ data, settings }) => {
// Normal List with Hyperlinks // Normal List with Hyperlinks
if (settings.displayType === "list") { if (settings.displayType === "list") {
return ( return (
<div className="mt-10 text-white/60 text-xs w-full"> <div className=" text-white/70 text-xs w-full backdrop-blur-sm rounded-t-xl overflow-hidden py-4 px-6 border-l border-t border-r border-[#2f3238]">
<Label {...data} />
{data.options.map((option, index) => ( {data.options.map((option, index) => (
<a <a
key={index} key={index}
href={option.href} // assuming `href` is available in your option object 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" className="block p-2.5 border-b border-white/10 last:border-0 hover:bg-sidebar/50 cursor-pointer"
onClick={() => handleSelection(option.value)}
> >
<p className="font-semibold">{option.label}</p> <p className="">
<span style={{ color: "grey" }}>{index + 1}.</span> {option.label}
</p>
</a> </a>
))} ))}
</div> </div>
@ -40,18 +64,8 @@ const OptionSelect = ({ data, settings }) => {
// Dropdown Menu // Dropdown Menu
return ( return (
<div className="mt-5 w-full"> <div className="mt-5 mb-5 w-full backdrop-blur-sm rounded-t-xl overflow-hidden py-4 px-6 border-l border-t border-r border-[#2f3238]">
<div className="flex flex-col"> <Label {...data} />
<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 <select
name="optionSelect" name="optionSelect"
id="optionSelect" id="optionSelect"
@ -59,7 +73,11 @@ const OptionSelect = ({ data, settings }) => {
required={true} required={true}
disabled={settings.disabled} disabled={settings.disabled}
className="shadow-xl 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" className="shadow-xl 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"
onChange={(e) => handleSelection(e.target.value)}
> >
<option value="placeholder" disabled selected>
Select an option
</option>
{settings.waitingForModels ? ( {settings.waitingForModels ? (
<option disabled={true} selected={true}> <option disabled={true} selected={true}>
-- waiting for models -- -- waiting for models --
@ -67,7 +85,7 @@ const OptionSelect = ({ data, settings }) => {
) : ( ) : (
data.options.map((option, index) => ( data.options.map((option, index) => (
<option key={index} value={option.value}> <option key={index} value={option.value}>
{option.label} <span style={{ color: "grey" }}>{index + 1}.</span> {option.label}
</option> </option>
)) ))
)} )}
@ -76,4 +94,24 @@ const OptionSelect = ({ data, settings }) => {
); );
}; };
const Label = ({ label, description }) => {
return (
<div className="hidden md:flex flex-col">
{label && (
<label
htmlFor="chatModel"
className="block input-label text-white text-opacity-60 text-xs font-medium py-1.5"
>
{label}
</label>
)}
{description && (
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
{description}
</p>
)}
</div>
);
};
export default OptionSelect; export default OptionSelect;

View File

@ -1,7 +1,8 @@
import React from "react"; import React, { useEffect, useState } from "react";
// import TextInput from './TextInput'; // import TextInput from './TextInput';
import OptionSelect from "@/components/WorkspaceChat/ChatContainer/DynamicInput/OptionSelect"; import OptionSelect from "@/components/WorkspaceChat/ChatContainer/DynamicInput/OptionSelect";
import { ArrowUUpLeft, Keyboard } from "@phosphor-icons/react"; import { Cursor, Keyboard } from "@phosphor-icons/react";
import PromptInput from "../PromptInput";
// import RangeSlider from './RangeSlider'; // import RangeSlider from './RangeSlider';
// import DatePicker from './DatePicker'; // import DatePicker from './DatePicker';
// import TimePicker from './TimePicker'; // import TimePicker from './TimePicker';
@ -25,31 +26,75 @@ const inputComponents = {
const DynamicInput = ({ const DynamicInput = ({
inputs, inputs,
isDynamicInput, isDynamicInput,
isForcedTextInput, submit,
setIsForcedTextInput, setMessage,
workspace,
message,
onChange,
inputDisabled,
buttonDisabled,
sendCommand,
}) => { }) => {
const [isForcedTextInput, setIsForcedTextInput] = useState(false);
useEffect(() => {
if (inputs?.type !== "text") {
setIsForcedTextInput(false);
}
}, [inputs]);
const InputComponent = inputComponents[inputs?.type] || null; const InputComponent = inputComponents[inputs?.type] || null;
if (!InputComponent) { if (!InputComponent) {
return null; // or any fallback UI return null; // or any fallback UI
} }
const renderPromptInput = () => {
if (inputs?.type === "text" || inputs === undefined || isForcedTextInput) {
return (
<PromptInput
className={
inputs !== undefined ? "bottom-8 md:-bottom-5" : "-bottom-2"
}
workspace={workspace}
message={message}
submit={submit}
onChange={onChange}
inputDisabled={inputDisabled}
buttonDisabled={buttonDisabled}
sendCommand={sendCommand}
/>
);
}
};
return ( return (
<div className="w-full px-4 fixed md:absolute bottom-10 left-0 z-10 md:z-0 flex justify-center items-center"> <div className="w-full md:px-4 fixed md:absolute bottom-10 left-0 z-10 md:z-0 flex justify-center items-center">
<div className="w-[600px]"> <div className="w-[600px]">
<InputComponent {...inputs} /> {inputs?.type !== "text" &&
isDynamicInput &&
inputs !== undefined &&
!isForcedTextInput ? (
<InputComponent
submit={submit}
setMessage={setMessage}
message={message}
{...inputs}
/>
) : (
renderPromptInput()
)}
{isDynamicInput && inputs != undefined && ( {isDynamicInput && inputs != undefined && (
<div className="flex justify-end"> <div className="w-full fixed absolute -bottom-8 left-0 z-10 md:z-0 flex justify-center items-center">
<button <button
type="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" className="transition-all w-fit duration-300 px-5 py-2.5 rounded-lg text-white/40 text-xs items-center flex gap-x-2 shadow-sm hover:text-white/60 focus:ring-gray-800"
onClick={() => setIsForcedTextInput(!isForcedTextInput)} onClick={() => setIsForcedTextInput(!isForcedTextInput)}
> >
{isForcedTextInput ? ( {isForcedTextInput ? (
<> <>
<ArrowUUpLeft className="h-5 w-5" /> back to options <Cursor className="h-5 w-5" /> Select an option
</> </>
) : ( ) : (
<> <>
<Keyboard className="h-5 w-5" /> Type another answer <Keyboard className="h-5 w-5" /> Type a response
</> </>
)} )}
</button> </button>

View File

@ -8,7 +8,7 @@ import { isMobile } from "react-device-detect";
import debounce from "lodash.debounce"; import debounce from "lodash.debounce";
export default function PromptInput({ export default function PromptInput({
className, className = "bottom-0",
workspace, workspace,
message, message,
submit, submit,

View File

@ -1,7 +1,6 @@
import Workspace from "@/models/workspace"; import Workspace from "@/models/workspace";
import handleChat from "@/utils/chat"; import handleChat from "@/utils/chat";
import { extractMetaData } from "@/utils/chat/extractMetaData"; import { extractMetaData } from "@/utils/chat/extractMetaData";
import { CursorClick, Keyboard } from "@phosphor-icons/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@ -21,16 +20,14 @@ export default function ChatContainer({
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [loadingResponse, setLoadingResponse] = useState(false); const [loadingResponse, setLoadingResponse] = useState(false);
const [chatHistory, setChatHistory] = useState(knownHistory); const [chatHistory, setChatHistory] = useState(knownHistory);
const [finalizedChatHistory, setFinalizedChatHistory] = const [finalizedChatHistory, setFinalizedChatHistory] = useState(knownHistory);
useState(knownHistory);
const [isForcedTextInput, setIsForcedTextInput] = useState(false);
const handleMessageChange = (event) => { const handleMessageChange = (event) => {
setMessage(event.target.value); setMessage(event.target.value);
}; };
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event?.preventDefault();
if (!message || message === "") return false; if (!message || message === "") return false;
const prevChatHistory = [ const prevChatHistory = [
@ -122,7 +119,6 @@ export default function ChatContainer({
_chatHistory[_chatHistory.length - 1].content = remainingText; _chatHistory[_chatHistory.length - 1].content = remainingText;
setFinalizedChatHistory(_chatHistory); setFinalizedChatHistory(_chatHistory);
setCurrentInputMeta(metaData); setCurrentInputMeta(metaData);
console.log("metaData", metaData);
} }
return; return;
@ -130,32 +126,6 @@ export default function ChatContainer({
loadingResponse === true && fetchReply(); loadingResponse === true && fetchReply();
}, [loadingResponse, chatHistory, workspace]); }, [loadingResponse, chatHistory, workspace]);
const renderInputComponent = () => {
if (
!isDynamicInput ||
currentInputMeta?.inputs?.type === "text" ||
currentInputMeta?.inputs === undefined ||
isForcedTextInput
) {
return (
<PromptInput
className={isDynamicInput && currentInputMeta?.inputs !== undefined ? "bottom-4" : "-bottom-2"}
workspace={workspace}
message={message}
submit={handleSubmit}
onChange={handleMessageChange}
inputDisabled={loadingResponse}
buttonDisabled={loadingResponse}
sendCommand={sendCommand}
/>
);
}
if (currentInputMeta?.inputs?.type !== "text") {
return <DynamicInput {...currentInputMeta} />;
}
};
return ( return (
<div <div
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }} style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
@ -168,25 +138,29 @@ export default function ChatContainer({
workspace={workspace} workspace={workspace}
sendCommand={sendCommand} sendCommand={sendCommand}
/> />
{renderInputComponent()} {isDynamicInput && currentInputMeta?.inputs?.type !== undefined ? (
{isDynamicInput && currentInputMeta?.inputs != undefined && ( <DynamicInput
<div className="w-full fixed md:absolute -bottom-1 left-0 z-10 md:z-0 flex justify-center items-center"> inputs={currentInputMeta?.inputs}
<button isDynamicInput={isDynamicInput}
type="button" submit={handleSubmit}
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" setMessage={setMessage}
onClick={() => setIsForcedTextInput(!isForcedTextInput)} workspace={workspace}
> message={message}
{isForcedTextInput ? ( onChange={handleMessageChange}
<> inputDisabled={loadingResponse}
<CursorClick className="h-5 w-5" /> use dynamic input buttonDisabled={loadingResponse}
</> sendCommand={sendCommand}
) : ( />
<> ) : (
<Keyboard className="h-5 w-5" /> use keyboard input <PromptInput
</> workspace={workspace}
)} message={message}
</button> submit={handleSubmit}
</div> onChange={handleMessageChange}
inputDisabled={loadingResponse}
buttonDisabled={loadingResponse}
sendCommand={sendCommand}
/>
)} )}
</div> </div>
</div> </div>

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react";
import Workspace from "@/models/workspace"; import Workspace from "@/models/workspace";
import LoadingChat from "./LoadingChat";
import ChatContainer from "./ChatContainer";
import paths from "@/utils/paths";
import ModalWrapper from "../ModalWrapper";
import { useParams } from "react-router-dom";
import { extractMetaData } from "@/utils/chat/extractMetaData"; import { extractMetaData } from "@/utils/chat/extractMetaData";
import paths from "@/utils/paths";
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import ModalWrapper from "../ModalWrapper";
import ChatContainer from "./ChatContainer";
import LoadingChat from "./LoadingChat";
export default function WorkspaceChat({ loading, workspace }) { export default function WorkspaceChat({ loading, workspace }) {
const { threadSlug = null } = useParams(); const { threadSlug = null } = useParams();

View File

@ -97,7 +97,6 @@ export default function handleChat(
}); });
} }
setChatHistory([..._chatHistory]); setChatHistory([..._chatHistory]);
console.log("chunk: ", [..._chatHistory]);
} else if (type === "finalizeResponseStream") { } else if (type === "finalizeResponseStream") {
const chatIdx = _chatHistory.findIndex((chat) => chat.uuid === uuid); const chatIdx = _chatHistory.findIndex((chat) => chat.uuid === uuid);
if (chatIdx !== -1) { if (chatIdx !== -1) {
@ -109,7 +108,6 @@ export default function handleChat(
_chatHistory[chatIdx] = updatedHistory; _chatHistory[chatIdx] = updatedHistory;
} }
setChatHistory([..._chatHistory]); setChatHistory([..._chatHistory]);
console.log("final: ", [..._chatHistory]);
} }
} }