- added range slider support

- added five-rating support
- enhanced movile experiance
This commit is contained in:
sherifButt 2024-03-28 12:45:05 +00:00
parent 17c089c3fb
commit e7960893e0
11 changed files with 316 additions and 89 deletions

View File

@ -1,9 +1,10 @@
import React from "react";
import Badge from "@/components/Generic/Badges/Badge";
import ToggleButton from "@/components/Generic/Inputs/ToggleSwitch";
import TitleBlock from "../TitleBlock";
export default function ToggleBlock({
content, // toggle content goes here
content, // toggled content goes here
initialChecked,
label,
onToggle,
@ -17,7 +18,6 @@ export default function ToggleBlock({
border,
bg,
Icon,
contentLocation,
disabled,
inline = false,
}) {

View File

@ -1,6 +1,7 @@
import React from "react";
export default function Button({
children,
onClick,
disabled,
icon: Icon,
@ -9,6 +10,7 @@ export default function Button({
iconSize = 18,
iconColor = "#D3D4D4",
textClass = "",
iconRight = false,
}) {
return (
<button
@ -16,12 +18,13 @@ export default function Button({
disabled={disabled}
className={`flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60 transition-all duration-150 ease-in-out ${className}`}
>
{Icon && <Icon size={iconSize} weight="bold" color={iconColor} />}
<div
{Icon && !iconRight && !children && <Icon size={iconSize} weight="bold" color={iconColor} />}
{children ? children : <div
className={`text-[#D3D4D4] text-xs font-bold leading-[18px] ${textClass}`}
>
{text}
</div>
</div>}
{Icon && iconRight && !children && <Icon size={iconSize} weight="bold" color={iconColor} />}
</button>
);
}

View File

@ -0,0 +1,19 @@
export default function Label({ label, description }) {
return (
<div className="flex flex-col">
{label && (
<label
htmlFor="chatModel"
className="block input-label text-white text-opacity-60 text-md font-medium py-1.5"
>
{label}
</label>
)}
{description && (
<p className="flex text-white text-opacity-60 text-sm font-medium py-1.5">
{description}
</p>
)}
</div>
);
}

View File

@ -1,7 +1,8 @@
import Label from "@/components/Generic/Typography/Label";
import { PaperPlaneRight } from "@phosphor-icons/react";
import { useEffect, useState } from "react";
const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace}) => {
const OptionSelect = ({ data, settings,type, submit, message, setMessage ,workspace}) => {
const [selectedOptions, setSelectedOptions] = useState([]);
const [submitMessage, setSubmitMessage] = useState(false);
@ -12,6 +13,7 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
}
}, [message]);
console.log("settings?.type.includes(dropdown)", settings)
const handleSelection = (value) => {
const currentIndex = selectedOptions.indexOf(value);
@ -33,7 +35,7 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
};
// Normal List with Hyperlinks
if (settings.displayType.includes("list") && workspace?.metaResponseSettings?.inputs?.config?.components?.optionsList?.isEnabled) {
if (settings?.displayType?.includes("list") || type?.includes("list") && workspace?.metaResponseSettings?.inputs?.config?.components?.optionsList?.isEnabled) {
return (
<div className=" text-white/70 text-sm w-full backdrop-blur-sm rounded-t-xl overflow-hidden py-4 px-6 border-l border-t border-r border-[#2f3238]">
<Label {...data} />
@ -60,10 +62,10 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
}
// Checkbox
if (settings.displayType.includes("checkbox") && workspace?.metaResponseSettings?.inputs?.config?.components?.multiSelectCheckboxes?.isEnabled) {
if (settings?.displayType?.includes("checkbox") || type?.includes("checkbox") && workspace?.metaResponseSettings?.inputs?.config?.components?.multiSelectCheckboxes?.isEnabled) {
return (
<div className="w-full p-4 backdrop-blur-sm rounded-t-xl overflow-hidden py-4 px-6 border-l border-t border-r border-[#2f3238]">
<Label label={data?.label} />
<Label {...data} />
<div className="pb-0 mt-2 grid grid-cols-1 md:grid-cols-2 gap-4 text-white/80 text-sm">
{data.options.map((option, index) => (
<label key={index} className="flex items-center space-x-2">
@ -96,7 +98,7 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
}
// Dropdown Menu
if (settings.displayType.includes("dropdown") && workspace?.metaResponseSettings?.inputs?.config?.components?.dropDownMenu?.isEnabled) {
if (settings?.displayType?.includes("dropdown") || type?.includes("dropdown") && workspace?.metaResponseSettings?.inputs?.config?.components?.dropDownMenu?.isEnabled) {
return (
<div className="mt-5 mb-5 w-full backdrop-blur-sm rounded-t-xl py-4 px-6 border-l border-t border-r border-[#2f3238]">
@ -104,9 +106,9 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
<select
name="optionSelect"
id="optionSelect"
multiple={settings.allowMultiple}
multiple={settings?.allowMultiple}
required={true}
disabled={settings.disabled}
disabled={settings?.disabled}
className="shadow-xl mt-3 bg-sidebar text-white text-sm 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);
@ -116,7 +118,7 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
<option value="placeholder" disabled selected>
Select an option
</option>
{settings.waitingForModels ? (
{settings?.waitingForModels ? (
<option disabled={true} selected={true}>
-- waiting for models --
</option>
@ -136,7 +138,7 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
return (
<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]">
<Label label={data?.label} />
<Label {...data} />
<div className=" pb-0 mt-2 grid grid-cols-1 md:grid-cols-2 gap-4 text-white/70 text-sm ">
{data.options.map((option, index) => (
<button
@ -165,24 +167,4 @@ const OptionSelect = ({ data, settings, submit, message, setMessage ,workspace})
};
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-md font-medium py-1.5"
>
{label}
</label>
)}
{description && (
<p className="text-white text-opacity-60 text-sm font-medium py-1.5">
{description}
</p>
)}
</div>
);
};
export default OptionSelect;

View File

@ -0,0 +1,83 @@
import Label from "@/components/Generic/Typography/Label";
import { useState, useMemo, useRef } from "react";
import { PaperPlaneRight } from "@phosphor-icons/react";
import Button from "@/components/Generic/Buttons/Button";
const RangeSlider = ({ data, settings, message, setMessage, onSubmit ,submit,workspace}) => {
const [value, setValue] = useState((data.min + data.max) / 2);
const [hasInteracted, setHasInteracted] = useState(false);
const mounted = useRef(null);
const handleChange = (event) => {
setValue(event.target.value);
setHasInteracted(true);
if (settings.showValue) {
setMessage(`Selected value: ${event.target.value}`);
}
};
const handleSubmit = () => {
submit();
};
// Calculate steps and generate markers
const markers = useMemo(() => {
const totalSteps = (data.max - data.min) / data.step;
const markerPositions = [];
for (let i = 0; i <= totalSteps; i++) {
const position = (i / totalSteps) * 97.5;
markerPositions.push(position);
}
return markerPositions;
}, [data.min, data.max, data.step]);
return (
<div ref={mounted} className="text-white/70 text-sm 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-row justify-between">
<Label {...data} />
<p className="range-slider-value flex items-center text-5xl text-white rounded-full px-5 py-2 drop-shadow-[0_35px_35px_rgba(0,0,0,0.25)] ">{value}</p>
</div>
<div className="relative w-full mt-10">
<input
type="range"
id="range-slider"
min={data.min}
max={data.max}
step={data.step}
value={value}
onChange={handleChange}
className="range-slider block py-2.5 border-b border-white/10 last:border-0 hover:bg-sidebar/50 cursor-pointer w-full bg-sky-200 "
/>
<div className="absolute top-0 mx-1.5 w-full flex justify-between">
{markers.map((position, index) => (
<div key={index} style={{ left: `${position}%` }} className="absolute bottom-0 h-3 w-0.5 bg-white/20"></div>
))}
</div>
</div>
<div className="flex justify-between items-center relative mb-6">
<span className="text-sm text-gray-500 dark:text-gray-400">
Min ({data.min})
</span>
{!hasInteracted &&<span className="text-sm text-gray-500 dark:text-gray-400">
Max ({data.max})
</span>}
{hasInteracted && (
<div className="absolute -bottom-2 right-0">
<Button
onClick={handleSubmit}
label="Submit"
icon={PaperPlaneRight}
iconRight
text={'submit'}
textClass="text-white/60"
/>
</div>
)}
</div>
</div>
);
};
export default RangeSlider;

View File

@ -0,0 +1,65 @@
import React, { useEffect, useState } from "react";
import { PaperPlaneRight, Star } from "@phosphor-icons/react";
import Label from "@/components/Generic/Typography/Label";
import Button from "@/components/Generic/Buttons/Button";
const StarRating = ({ data, submit, setMessage, message }) => {
const [rating, setRating] = useState(data.defaultValue || 0);
const [hoveredRating, setHoveredRating] = useState(undefined);
const [submitRating, setSubmitRating] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (submitRating) {
setIsSubmitting(true)
submit();
setSubmitRating(false);
}
}, [rating]);
const handleRating = (rate) => {
setRating(rate);
setMessage(`Selected rating: ${rate} stars of ${data.max} stars`);
setSubmitRating(true);
};
return (
<div className=" text-white/70 text-sm 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 md:flex-row justify-between mb-6">
<Label {...data} />
<div className="mx-auto mt-6 md:mx-0 flex items-center gap-2">
{[...Array(data.max)].map((_, index) => (
<button
key={index}
onMouseEnter={() => setHoveredRating(index + 1)}
onMouseLeave={() => setHoveredRating(undefined)}
onClick={() => handleRating(index + 1)}
className="mx-1"
disabled={isSubmitting}
>
{rating > index || hoveredRating > index ? (
<Star size={34} color="#ffd700" weight="fill" fill="#ffd700" />
) : (
<Star size={34} color="#ffd700" />
)}
</button>
))}
</div>
</div>
{/* <div className="flex justify-end items-center mt-4">
<Button
onClick={handleSubmit}
label="Submit"
icon={PaperPlaneRight}
iconRight
text={'Submit'}
textClass="text-white/60"
/>
</div> */}
</div>
);
};
export default StarRating;

View File

@ -1,8 +1,12 @@
import React, { useEffect, useState } from "react";
// import TextInput from './TextInput';
import OptionSelect from "@/components/WorkspaceChat/ChatContainer/MetaInputs/OptionSelect";
import { Cursor, Keyboard } from "@phosphor-icons/react";
import { Cursor, Eye, List, Keyboard, X } from "@phosphor-icons/react";
import PromptInput from "../PromptInput";
import RangeSlider from "./RangeSlider";
import Rating from "./Rating";
import Button from "@/components/Generic/Buttons/Button";
import { EyeClosed } from "@phosphor-icons/react/dist/ssr";
// import RangeSlider from './RangeSlider';
// import DatePicker from './DatePicker';
// import TimePicker from './TimePicker';
@ -17,12 +21,12 @@ const inputComponents = {
list: OptionSelect,
buttons: OptionSelect,
dropdown: OptionSelect,
// range: RangeSlider,
range: RangeSlider,
// date: DatePicker,
// time: TimePicker,
// datetime: DateTimePicker,
// file: FileUpload,
// rating: Rating,
rating: Rating,
};
const MetaInputs = ({
@ -37,6 +41,7 @@ const MetaInputs = ({
sendCommand,
}) => {
const [isForcedTextInput, setIsForcedTextInput] = useState(false);
const [showMetaInputs, setShowMetaInputs] = useState(true);
useEffect(() => {
setIsForcedTextInput(inputs?.type === "text");
@ -53,55 +58,97 @@ const MetaInputs = ({
workspace?.metaResponse && inputs !== undefined && !isForcedTextInput;
return (
<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-[700px]">
<div className=" w-full md:px-4 fixed md:absolute bottom-10 left-0 z-10 md:z-0 flex flex-col justify-center items-center">
{!showMetaInputs && (
<Button
type="button"
className="text-right -mb-7 md:mb-0 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={() => setShowMetaInputs(!showMetaInputs)}
>
{showMetaInputs ? (
<>
{" "}
<EyeClosed className="w-4 h-4" /> Hide{" "}
</>
) : (
<>
{" "}
<Eye className="w-4 h-4" /> Show{" "}
</>
)} Inputs
</Button>
)}
{showMetaInputs && (
<div className="w-full md:w-[700px]">
{workspace?.metaResponse && inputs != undefined && (
<div className="w-full backdrop-blur-sm absolute -bottom-10 md:-bottom-8 left-0 z-10 md:z-0 flex justify-center items-center gap-6">
<Button
type="button"
className="text-right mb-2 md:mb-0 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={() => setShowMetaInputs(!showMetaInputs)}
>
{showMetaInputs ? (
<>
{" "}
<EyeClosed className="w-4 h-4" /> Hide{" "}
</>
) : (
<>
{" "}
<Eye className="w-4 h-4" /> Show{" "}
</>
)}{" "}
{shouldShowMetaInputs
? inputs?.settings?.displayType
: "text input"}
</Button>
<Button
type="button"
className="mb-2 md:mb-0 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)}
>
{isForcedTextInput ? (
<>
<Cursor className="h-4 w-4" /> Use{" "}
{inputs?.settings?.displayType}
</>
) : (
<>
<Keyboard className="h-5 w-5" /> Ues Keyboard
</>
)}
</Button>
</div>
)}
{shouldShowMetaInputs ? (
<InputComponent
submit={submit}
setMessage={setMessage}
message={message}
workspace={workspace}
onChange={onChange}
inputDisabled={inputDisabled}
buttonDisabled={buttonDisabled}
sendCommand={sendCommand}
{...inputs}
/>
) : (
<PromptInput
className={
inputs === undefined ? "-bottom-2" : "bottom-8 md:-bottom-5"
}
workspace={workspace}
message={message}
submit={submit}
onChange={onChange}
inputDisabled={inputDisabled}
buttonDisabled={buttonDisabled}
sendCommand={sendCommand}
/>
)}
{workspace?.metaResponse && inputs != undefined && (
<div className="w-full absolute -bottom-8 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/40 text-xs items-center flex gap-x-2 shadow-sm hover:text-white/60 focus:ring-gray-800"
onClick={() => setIsForcedTextInput(!isForcedTextInput)}
>
{isForcedTextInput ? (
<>
<Cursor className="h-5 w-5" /> Select an option
</>
) : (
<>
<Keyboard className="h-5 w-5" /> Type a response
</>
)}
</button>
</div>
)}
</div>
{shouldShowMetaInputs ? (
<InputComponent
submit={submit}
setMessage={setMessage}
message={message}
workspace={workspace}
onChange={onChange}
inputDisabled={inputDisabled}
buttonDisabled={buttonDisabled}
sendCommand={sendCommand}
{...inputs}
/>
) : (
<PromptInput
className={
inputs === undefined ? "-bottom-2" : "bottom-8 md:-bottom-5"
}
workspace={workspace}
message={message}
submit={submit}
onChange={onChange}
inputDisabled={inputDisabled}
buttonDisabled={buttonDisabled}
sendCommand={sendCommand}
/>
)}
</div>
)}
</div>
);
};

View File

@ -21,7 +21,8 @@ export default function ChatContainer({
const [message, setMessage] = useState("");
const [loadingResponse, setLoadingResponse] = useState(false);
const [chatHistory, setChatHistory] = useState(knownHistory);
const [finalizedChatHistory, setFinalizedChatHistory] = useState(knownHistory);
const [finalizedChatHistory, setFinalizedChatHistory] =
useState(knownHistory);
const handleMessageChange = (event) => {
setMessage(event.target.value);
@ -135,7 +136,8 @@ export default function ChatContainer({
workspace={workspace}
sendCommand={sendCommand}
/>
{workspace?.metaResponse && currentInputMeta?.inputs?.type !== undefined ? (
{workspace?.metaResponse &&
currentInputMeta?.inputs?.type !== undefined ? (
<MetaInputs
inputs={currentInputMeta?.inputs}
isMetaInputs={isMetaInputs}

View File

@ -115,7 +115,12 @@ export default function MetaResponseSettings({ workspace, setWorkspace }) {
featureSettings.config.promptSchema.list[
featureSettings.config.promptSchema.active
].content;
Object.keys(featureSettings.config.components).map((component) => {
const componentSettings = featureSettings.config.components[component];
if (componentSettings.isEnabled) {
openAiPrompt += componentSettings.schema;
}
});
}
});
return openAiPrompt;

View File

@ -6,6 +6,7 @@
export const extractMetaData = (textResponse) => {
console.log("textResponse", textResponse);
if (!textResponse) return { remainingText: "", metaData: {} };
let remainingText = textResponse;
let inString = false;
let char, prevChar;
@ -14,7 +15,7 @@ export const extractMetaData = (textResponse) => {
let startIndex = null;
let extractedObjects = {}; // Keep as an object as requested
for (let i = 0; i < textResponse.length; i++) {
for (let i = 0; i < textResponse?.length; i++) {
char = textResponse[i];
if (char === '"' && prevChar !== "\\") inString = !inString;
if (inString) continue;

View File

@ -107,6 +107,7 @@ const WorkspaceMetaResponse = {
isEnabled: true,
isDefault: true,
options: [],
schema:"\n```json\n{\n \"inputs\": {\n \"type\":\"options\",\n \"data\":{\n \"options\":[\n {\n \"label\":\"Restart Router\",\n \"value\":\"restart_router\"\n },\n {\n \"label\":\"Check Service Status\",\n \"value\":\"check_service_status\"\n },\n ...\n ],\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n },\n \"settings\":{\n \"allowMultiple\":false,\n \"displayType\":\"buttons\"\n }\n }\n}\n```",
description: "Chat will provide answers with the LLM's general knowledge and document context that is found.",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/options-buttons",
},
@ -114,6 +115,7 @@ const WorkspaceMetaResponse = {
isEnabled: false,
isDefault: false,
options: [],
schema:"\n```json\n{\n \"inputs\": {\n \"type\":\"options\",\n \"data\":{\n \"options\":[\n {\n \"label\":\"Restart Router\",\n \"value\":\"restart_router\"\n },\n {\n \"label\":\"Check Service Status\",\n \"value\":\"check_service_status\"\n },\n ...\n ],\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n },\n \"settings\":{\n \"allowMultiple\":false,\n \"displayType\":\"list\"\n }\n }\n}\n```",
description: "Best suited for expansion on a topic",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/options-list",
},
@ -121,6 +123,7 @@ const WorkspaceMetaResponse = {
isEnabled: false,
isDefault: false,
options: [],
schema:"\n```json\n{\n \"inputs\": {\n \"type\":\"options\",\n \"data\":{\n \"options\":[\n {\n \"label\":\"Restart Router\",\n \"value\":\"restart_router\"\n },\n {\n \"label\":\"Check Service Status\",\n \"value\":\"check_service_status\"\n },\n ...\n ],\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n },\n \"settings\":{\n \"allowMultiple\":false,\n \"displayType\":\"checkbox\"\n }\n }\n}\n```",
description: "Chat will provide answers with the LLM's general knowledge and document context that is found.",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/multi-select-checkboxes",
},
@ -128,9 +131,26 @@ const WorkspaceMetaResponse = {
isEnabled: false,
isDefault: false,
options: [],
schema:"\n```json\n{\n \"inputs\": {\n \"type\":\"options\",\n \"data\":{\n \"options\":[\n {\n \"label\":\"Restart Router\",\n \"value\":\"restart_router\"\n },\n {\n \"label\":\"Check Service Status\",\n \"value\":\"check_service_status\"\n },\n ...\n ],\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n },\n \"settings\":{\n \"allowMultiple\":false,\n \"displayType\":\"dropdown\"\n }\n }\n}\n```",
description: "Drop Down menu best to select between functional derisions, ie: continue, Repeat or Move to a new sequence.. etc",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/dropdown-menu",
},
range: {
isEnabled: false,
isDefault: false,
options: [],
schema:"\n```json\n{\n \"inputs\": {\n \"type\":\"range\",\n \"data\":{\n \"min\":1,\n \"max\":10,\n \"step\":1,\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n },\n \"settings\":{\n \"showValue\":true\n }\n }\n}\n```",
description: "Range best to select between between a certain range, ie: 1-10, 1-100, 1-1000.. etc",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/dropdown-menu",
},
rating: {
isEnabled: false,
isDefault: false,
options: [],
schema:"```json\n{\n\"inputs\": {\n \"type\":\"rating\",\n \"data\":{\n \"max\":5,\n \"defaultValue\":1,\n \"icon\":\"star\",\n \"label\":\"Select Server \",\n \"description\":\"list of servers as described\"\n }\n }\n}\n```",
description: "Rating best for user to insert a rating, usually between one and five.",
infoLink: "https://docs.anythingllm.com/docs/meta-response/inputs/dropdown-menu",
},
},
},
permissions: ["user"],