Revert back save button thread setting

This commit is contained in:
Faisal Amir 2024-01-03 22:02:06 +07:00
parent 0f333c35f5
commit a653c58384
12 changed files with 219 additions and 634 deletions

View File

@ -1,76 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react' import React from 'react'
import { import { Switch } from '@janhq/uikit'
Switch,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from '@janhq/uikit'
// import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { InfoIcon } from 'lucide-react' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
// import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom'
// import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom'
type Props = { type Props = {
name: string name: string
title: string title: string
description: string
value: boolean
checked: boolean checked: boolean
onBlur: () => void
onChange: (e: boolean) => void
} }
const Checkbox: React.FC<Props> = ({ const Checkbox: React.FC<Props> = ({ name, title, checked }) => {
name, const { updateModelParameter } = useUpdateModelParameters()
value, const threadId = useAtomValue(getActiveThreadIdAtom)
title,
checked,
description,
onChange,
onBlur,
}) => {
// const { updateModelParameter } = useUpdateModelParameters()
// const threadId = useAtomValue(getActiveThreadIdAtom)
// const onCheckedChange = (checked: boolean) => { const onCheckedChange = (checked: boolean) => {
// if (!threadId) return if (!threadId) return
updateModelParameter(threadId, name, checked)
// updateModelParameter(threadId, name, checked) }
// }
return ( return (
<div className="flex justify-between"> <div className="flex justify-between">
<div className="mb-1 flex items-center gap-x-2"> <p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300"> <Switch checked={checked} onCheckedChange={onCheckedChange} />
{title}
</p>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="top" className="max-w-[240px]">
<span>{description}</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<Switch
name={name}
checked={value !== undefined ? value : checked}
onCheckedChange={(e) => onChange(e)}
onBlur={onBlur}
/>
</div> </div>
) )
} }

View File

@ -16,14 +16,13 @@ import {
} from '@janhq/uikit' } from '@janhq/uikit'
import { motion as m } from 'framer-motion' import { motion as m } from 'framer-motion'
import { useAtomValue } from 'jotai'
import { import {
MessageCircleIcon, MessageCircleIcon,
SettingsIcon, SettingsIcon,
MonitorIcon, MonitorIcon,
LayoutGridIcon, LayoutGridIcon,
Twitter, // Twitter,
Github, // Github,
} from 'lucide-react' } from 'lucide-react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -34,13 +33,8 @@ import { MainViewState } from '@/constants/screens'
import { useMainViewState } from '@/hooks/useMainViewState' import { useMainViewState } from '@/hooks/useMainViewState'
import { threadSettingFormUpdateAtom } from '@/helpers/atoms/Thread.atom'
export default function RibbonNav() { export default function RibbonNav() {
const { mainViewState, setMainViewState } = useMainViewState() const { mainViewState, setMainViewState } = useMainViewState()
const threadSettingFormUpdate = useAtomValue(threadSettingFormUpdateAtom)
const [showModalUpdateThreadSetting, setshowModalUpdateThreadSetting] =
useState({ show: false, view: mainViewState })
const onMenuClick = (state: MainViewState) => { const onMenuClick = (state: MainViewState) => {
if (mainViewState === state) return if (mainViewState === state) return
@ -70,22 +64,22 @@ export default function RibbonNav() {
}, },
] ]
const linksMenu = [ // const linksMenu = [
{ // {
name: 'Twitter', // name: 'Twitter',
icon: ( // icon: (
<Twitter size={20} className="flex-shrink-0 text-muted-foreground" /> // <Twitter size={20} className="flex-shrink-0 text-muted-foreground" />
), // ),
link: 'https://twitter.com/janhq_', // link: 'https://twitter.com/janhq_',
}, // },
{ // {
name: 'Github', // name: 'Github',
icon: ( // icon: (
<Github size={20} className="flex-shrink-0 text-muted-foreground" /> // <Github size={20} className="flex-shrink-0 text-muted-foreground" />
), // ),
link: 'https://github.com/janhq/jan', // link: 'https://github.com/janhq/jan',
}, // },
] // ]
const secondaryMenus = [ const secondaryMenus = [
{ {
@ -132,23 +126,7 @@ export default function RibbonNav() {
'relative flex w-full flex-shrink-0 cursor-pointer items-center justify-center', 'relative flex w-full flex-shrink-0 cursor-pointer items-center justify-center',
isActive && 'z-10' isActive && 'z-10'
)} )}
onClick={() => { onClick={() => onMenuClick(primary.state)}
if (
threadSettingFormUpdate &&
mainViewState === MainViewState.Thread
) {
setshowModalUpdateThreadSetting({
show: true,
view: primary.state,
})
} else {
setshowModalUpdateThreadSetting({
show: false,
view: mainViewState,
})
onMenuClick(primary.state)
}
}}
> >
{primary.icon} {primary.icon}
</div> </div>
@ -170,7 +148,8 @@ export default function RibbonNav() {
</div> </div>
<div> <div>
<> {/* Temporary hidden social media until we finalize design */}
{/* <>
{linksMenu {linksMenu
.filter((link) => !!link) .filter((link) => !!link)
.map((link, i) => { .map((link, i) => {
@ -195,7 +174,8 @@ export default function RibbonNav() {
</div> </div>
) )
})} })}
</> </> */}
{secondaryMenus {secondaryMenus
.filter((secondary) => !!secondary) .filter((secondary) => !!secondary)
.map((secondary, i) => { .map((secondary, i) => {
@ -210,23 +190,7 @@ export default function RibbonNav() {
'relative flex w-full flex-shrink-0 cursor-pointer items-center justify-center', 'relative flex w-full flex-shrink-0 cursor-pointer items-center justify-center',
isActive && 'z-10' isActive && 'z-10'
)} )}
onClick={() => { onClick={() => onMenuClick(secondary.state)}
if (
threadSettingFormUpdate &&
mainViewState === MainViewState.Thread
) {
setshowModalUpdateThreadSetting({
show: true,
view: secondary.state,
})
} else {
setshowModalUpdateThreadSetting({
show: false,
view: mainViewState,
})
onMenuClick(secondary.state)
}
}}
> >
{secondary.icon} {secondary.icon}
</div> </div>
@ -248,53 +212,6 @@ export default function RibbonNav() {
</div> </div>
</div> </div>
</div> </div>
<Modal
open={showModalUpdateThreadSetting.show}
onOpenChange={() =>
setshowModalUpdateThreadSetting({
show: false,
view: mainViewState,
})
}
>
<ModalContent>
<ModalHeader>
<ModalTitle>
<div className="text-lg">Unsave changes</div>
</ModalTitle>
<ModalDescription>
<p className="mb-2">
You have unsave changes. Are you sure you want to leave this
page?
</p>
</ModalDescription>
</ModalHeader>
<ModalFooter>
<div className="flex gap-x-2">
<ModalClose asChild>
<Button themes="secondary" block>
Stay
</Button>
</ModalClose>
<Button
themes="danger"
autoFocus
block
onClick={() => {
setshowModalUpdateThreadSetting({
show: false,
view: mainViewState,
})
onMenuClick(showModalUpdateThreadSetting.view)
}}
>
Leave
</Button>
</div>
</ModalFooter>
</ModalContent>
</Modal>
</div> </div>
) )
} }

View File

@ -181,7 +181,7 @@ const TopBar = () => {
/> />
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-medium text-black dark:text-muted-foreground"> <span className="font-medium text-black dark:text-muted-foreground">
View as JSON Edit Threads Settings
</span> </span>
<span className="mt-1 text-muted-foreground"> <span className="mt-1 text-muted-foreground">
Opens thread.json. Changes affect this thread Opens thread.json. Changes affect this thread

View File

@ -1,71 +1,39 @@
import { import { Textarea } from '@janhq/uikit'
Textarea,
Tooltip,
TooltipPortal,
TooltipArrow,
TooltipContent,
TooltipTrigger,
} from '@janhq/uikit'
// import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { InfoIcon } from 'lucide-react' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
// import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom'
// import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom'
type Props = { type Props = {
title: string title: string
name: string name: string
description: string
placeholder: string placeholder: string
value: string value: string
onBlur: () => void
onChange: () => void
} }
const ModelConfigInput: React.FC<Props> = ({ const ModelConfigInput: React.FC<Props> = ({
title, title,
name, name,
value, value,
description,
placeholder, placeholder,
onChange,
onBlur,
}) => { }) => {
// const { updateModelParameter } = useUpdateModelParameters() const { updateModelParameter } = useUpdateModelParameters()
// const threadId = useAtomValue(getActiveThreadIdAtom) const threadId = useAtomValue(getActiveThreadIdAtom)
// const onValueChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const onValueChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
// if (!threadId) return if (!threadId) return
// updateModelParameter(threadId, name, e.target.value) updateModelParameter(threadId, name, e.target.value)
// } }
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="mb-4 flex items-center gap-x-2"> <p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
{title}
</p>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="top" className="max-w-[240px]">
<span>{description}</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<Textarea <Textarea
name={name}
placeholder={placeholder} placeholder={placeholder}
onChange={onChange} onChange={onValueChanged}
onBlur={onBlur}
value={value} value={value}
/> />
</div> </div>

View File

@ -1,30 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react' import React from 'react'
import { Slider, Input, TooltipPortal } from '@janhq/uikit' import { Slider, Input } from '@janhq/uikit'
import { import { useAtomValue } from 'jotai'
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipArrow,
} from '@janhq/uikit'
// import { useAtomValue } from 'jotai'
import { InfoIcon } from 'lucide-react'
// import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
// import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom' import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom'
type Props = { type Props = {
name: string name: string
description: string
title: string title: string
min: number min: number
max: number max: number
step: number step: number
value: number[] value: number
onChange: (e: any) => void
onBlur: (e: any) => void
} }
const SliderRightPanel: React.FC<Props> = ({ const SliderRightPanel: React.FC<Props> = ({
@ -32,50 +21,29 @@ const SliderRightPanel: React.FC<Props> = ({
title, title,
min, min,
max, max,
description,
onChange,
onBlur,
step, step,
value, value,
}) => { }) => {
// const { updateModelParameter } = useUpdateModelParameters() const { updateModelParameter } = useUpdateModelParameters()
// const threadId = useAtomValue(getActiveThreadIdAtom) const threadId = useAtomValue(getActiveThreadIdAtom)
// const onValueChanged = (e: number[]) => { const onValueChanged = (e: number[]) => {
// if (!threadId) return if (!threadId) return
// updateModelParameter(threadId, name, e[0])
// } updateModelParameter(threadId, name, e[0])
}
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="mb-4 flex items-center gap-x-2"> <p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
{title}
</p>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="top" className="max-w-[240px]">
<span>{description}</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<div className="flex items-center gap-x-4"> <div className="flex items-center gap-x-4">
<div className="relative w-full"> <div className="relative w-full">
<Slider <Slider
defaultValue={value} value={[value]}
onValueChange={(e) => onChange(e[0])} onValueChange={onValueChanged}
onChange={(e) => onChange(e)}
onBlur={onBlur}
name={name}
min={min} min={min}
max={max} max={max}
step={step} step={step}
value={value}
/> />
<div className="relative mt-2 flex items-center justify-between text-gray-400"> <div className="relative mt-2 flex items-center justify-between text-gray-400">
<p className="text-sm">{min}</p> <p className="text-sm">{min}</p>
@ -89,10 +57,8 @@ const SliderRightPanel: React.FC<Props> = ({
className="-mt-4 h-8 w-16" className="-mt-4 h-8 w-16"
min={min} min={min}
max={max} max={max}
name={name} value={String(value)}
value={value[0] || String(value[0])} onChange={(e) => onValueChanged([Number(e.target.value)])}
onChange={(e) => onChange([e.target.value])}
onBlur={onBlur}
/> />
</div> </div>
</div> </div>

View File

@ -7,8 +7,6 @@ import {
} from '@janhq/core' } from '@janhq/core'
import { atom } from 'jotai' import { atom } from 'jotai'
export const threadSettingFormUpdateAtom = atom<boolean>(false)
/** /**
* Stores the current active thread id. * Stores the current active thread id.
*/ */

View File

@ -25,7 +25,11 @@ export default function useUpdateModelParameters() {
const activeThreadState = useAtomValue(activeThreadStateAtom) const activeThreadState = useAtomValue(activeThreadStateAtom)
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
const updateModelParameter = async (threadId: string, values: any) => { const updateModelParameter = async (
threadId: string,
name: string,
value: number | boolean | string
) => {
const thread = threads.find((thread) => thread.id === threadId) const thread = threads.find((thread) => thread.id === threadId)
if (!thread) { if (!thread) {
console.error(`Thread ${threadId} not found`) console.error(`Thread ${threadId} not found`)
@ -36,10 +40,9 @@ export default function useUpdateModelParameters() {
console.error('No active thread') console.error('No active thread')
return return
} }
const updatedModelParams: ModelParams = { const updatedModelParams: ModelParams = {
...activeModelParams, ...activeModelParams,
...values, [name]: value,
} }
// update the state // update the state

View File

@ -10,7 +10,7 @@ import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom' import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
const EngineSetting = (props: { form: any }) => { const EngineSetting = () => {
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
const selectedModel = useAtomValue(selectedModelAtom) const selectedModel = useAtomValue(selectedModelAtom)
@ -24,7 +24,7 @@ const EngineSetting = (props: { form: any }) => {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
{settingComponentBuilder(componentData, props.form)} {settingComponentBuilder(componentData)}
</div> </div>
) )
} }

View File

@ -12,7 +12,7 @@ import settingComponentBuilder from './settingComponentBuilder'
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom' import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
const ModelSetting = (props: { form: any }) => { const ModelSetting = () => {
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
const selectedModel = useAtomValue(selectedModelAtom) const selectedModel = useAtomValue(selectedModelAtom)
@ -26,7 +26,7 @@ const ModelSetting = (props: { form: any }) => {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
{settingComponentBuilder(componentData, props.form)} {settingComponentBuilder(componentData)}
</div> </div>
) )
} }

View File

@ -1,7 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
import { FormControl, FormField, FormItem, FormMessage } from '@janhq/uikit'
import Checkbox from '@/containers/Checkbox' import Checkbox from '@/containers/Checkbox'
import ModelConfigInput from '@/containers/ModelConfigInput' import ModelConfigInput from '@/containers/ModelConfigInput'
import Slider from '@/containers/Slider' import Slider from '@/containers/Slider'
@ -34,7 +31,6 @@ type CheckboxData = {
const settingComponentBuilder = ( const settingComponentBuilder = (
componentData: SettingComponentData[], componentData: SettingComponentData[],
form?: any,
onlyPrompt?: boolean onlyPrompt?: boolean
) => { ) => {
const components = componentData const components = componentData
@ -46,80 +42,36 @@ const settingComponentBuilder = (
case 'slider': case 'slider':
const { min, max, step, value } = data.controllerData as SliderData const { min, max, step, value } = data.controllerData as SliderData
return ( return (
<FormField
key={data.title}
control={form.control}
name={data.name}
render={({ field }) => (
<>
<FormItem>
<FormControl>
<Slider <Slider
key={data.name} key={data.name}
title={data.title} title={data.title}
description={data.description}
min={min} min={min}
max={max} max={max}
step={step} step={step}
{...field} value={value}
value={[field.value || value]} name={data.name}
/>
</FormControl>
<FormMessage />
</FormItem>
</>
)}
/> />
) )
case 'input': case 'input':
const { placeholder, value: textValue } = const { placeholder, value: textValue } =
data.controllerData as InputData data.controllerData as InputData
return ( return (
<FormField
key={data.title}
control={form.control}
name={data.name}
render={({ field }) => (
<FormItem>
<FormControl>
<ModelConfigInput <ModelConfigInput
title={data.title} title={data.title}
key={data.name} key={data.name}
description={data.description} name={data.name}
placeholder={placeholder} placeholder={placeholder}
{...field} value={textValue}
value={field.value}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/> />
) )
case 'checkbox': case 'checkbox':
const { checked } = data.controllerData as CheckboxData const { checked } = data.controllerData as CheckboxData
return ( return (
<FormField
key={data.title}
control={form.control}
name={data.name}
render={({ field }) => (
<>
<FormItem>
<FormControl>
<Checkbox <Checkbox
key={data.name} key={data.name}
description={data.description} name={data.name}
title={data.title} title={data.title}
{...field} checked={checked}
checked={field.value || checked}
value={field.value}
/>
</FormControl>
<FormMessage />
</FormItem>
</>
)}
/> />
) )
default: default:

View File

@ -1,25 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect } from 'react' import React from 'react'
import { FieldValues, useForm } from 'react-hook-form'
import { getUserSpace, openFileExplorer, joinPath } from '@janhq/core' import { getUserSpace, openFileExplorer, joinPath } from '@janhq/core'
import { import { Input, Textarea } from '@janhq/uikit'
Input,
Textarea,
Form,
Button,
FormField,
FormItem,
Tooltip,
TooltipArrow,
TooltipContent,
TooltipPortal,
TooltipTrigger,
FormControl,
} from '@janhq/uikit'
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { atom, useAtomValue } from 'jotai'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -30,14 +16,10 @@ import DropdownListSidebar, {
selectedModelAtom, selectedModelAtom,
} from '@/containers/DropdownListSidebar' } from '@/containers/DropdownListSidebar'
import { currentPromptAtom } from '@/containers/Providers/Jotai'
import { useCreateNewThread } from '@/hooks/useCreateNewThread' import { useCreateNewThread } from '@/hooks/useCreateNewThread'
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
import { getConfigurationsData } from '@/utils/componentSettings' import { getConfigurationsData } from '@/utils/componentSettings'
import { toRuntimeParams, toSettingParams } from '@/utils/model_param' import { toSettingParams } from '@/utils/model_param'
import EngineSetting from '../EngineSetting' import EngineSetting from '../EngineSetting'
import ModelSetting from '../ModelSetting' import ModelSetting from '../ModelSetting'
@ -46,9 +28,7 @@ import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
import { import {
activeThreadAtom, activeThreadAtom,
getActiveThreadIdAtom,
getActiveThreadModelParamsAtom, getActiveThreadModelParamsAtom,
threadSettingFormUpdateAtom,
threadStatesAtom, threadStatesAtom,
} from '@/helpers/atoms/Thread.atom' } from '@/helpers/atoms/Thread.atom'
@ -60,39 +40,11 @@ const Sidebar: React.FC = () => {
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
const selectedModel = useAtomValue(selectedModelAtom) const selectedModel = useAtomValue(selectedModelAtom)
const { updateThreadMetadata } = useCreateNewThread() const { updateThreadMetadata } = useCreateNewThread()
const { updateModelParameter } = useUpdateModelParameters()
const threadStates = useAtomValue(threadStatesAtom) const threadStates = useAtomValue(threadStatesAtom)
const threadId = useAtomValue(getActiveThreadIdAtom)
const modelEngineParams = toSettingParams(activeModelParams) const modelEngineParams = toSettingParams(activeModelParams)
const componentDataEngineSetting = getConfigurationsData(modelEngineParams) const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
const modelRuntimeParams = toRuntimeParams(activeModelParams)
const componentDataRuntimeSetting = getConfigurationsData(modelRuntimeParams)
const setThreadSettingFormUpdate = useSetAtom(threadSettingFormUpdateAtom)
const [currentPrompt] = useAtom(currentPromptAtom)
const componentData = [
...[
{ name: 'title', controllerData: { value: activeThread?.title } },
{
name: 'instructions',
controllerData: { value: activeThread?.assistants[0].instructions },
},
],
...componentDataRuntimeSetting,
...componentDataEngineSetting,
]
const defaultValues = componentData.reduce(
(obj: any, item: { name: any; controllerData: any }) =>
Object.assign(obj, {
[item.name]: item.controllerData.value
? item.controllerData.value
: item.controllerData.checked,
}),
{}
)
const onReviewInFinderClick = async (type: string) => { const onReviewInFinderClick = async (type: string) => {
if (!activeThread) return if (!activeThread) return
@ -160,79 +112,6 @@ const Sidebar: React.FC = () => {
openFileExplorer(fullPath) openFileExplorer(fullPath)
} }
const form = useForm({ defaultValues })
const filterChangedFormFields = <T extends FieldValues>(
allFields: T,
dirtyFields: Partial<Record<keyof T, boolean | boolean[]>>
): Partial<T> => {
const changedFieldValues = Object.keys(dirtyFields).reduce(
(acc, currentField) => {
const isDirty = Array.isArray(dirtyFields[currentField])
? (dirtyFields[currentField] as boolean[]).some((value) => {
value === true
})
: dirtyFields[currentField] === true
if (isDirty) {
return {
...acc,
[currentField]: allFields[currentField],
}
}
return acc
},
{} as Partial<T>
)
return changedFieldValues
}
const isEngineParamsChanges = componentDataEngineSetting.some((x) =>
Object.keys(form.formState.dirtyFields).includes(x.name)
)
const onSubmit = async (values: any) => {
if (!threadId) return
if (!activeThread) return
if (Object.keys(form.formState.dirtyFields).length) {
if (
Object.keys(form.formState.dirtyFields).includes('title') ||
Object.keys(form.formState.dirtyFields).includes('instructions')
) {
updateThreadMetadata({
...activeThread,
title: values.title || activeThread.title,
assistants: [
{
...activeThread.assistants[0],
instructions:
values.instructions || activeThread?.assistants[0].instructions,
},
],
})
}
updateModelParameter(
threadId,
filterChangedFormFields(values, form.formState.dirtyFields)
)
form.reset({}, { keepValues: true })
}
}
const onCancel = () => {
form.reset()
}
// Detect event click after changes value in form to showing tooltip on save button
useEffect(() => {
if (Object.keys(form.formState.dirtyFields).length !== 0) {
setThreadSettingFormUpdate(true)
} else {
setThreadSettingFormUpdate(false)
}
}, [form.formState, setThreadSettingFormUpdate])
return ( return (
<div <div
className={twMerge( className={twMerge(
@ -242,8 +121,6 @@ const Sidebar: React.FC = () => {
: 'w-0 translate-x-full opacity-0' : 'w-0 translate-x-full opacity-0'
)} )}
> >
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div <div
className={twMerge( className={twMerge(
'flex flex-col gap-1 delay-200', 'flex flex-col gap-1 delay-200',
@ -254,30 +131,20 @@ const Sidebar: React.FC = () => {
<div> <div>
<label <label
id="thread-title" id="thread-title"
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300" className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
> >
Title Title
</label> </label>
<FormField
key={activeThread?.title}
control={form.control}
name="title"
render={({ field }) => (
<>
<FormItem>
<FormControl>
<Input <Input
id="thread-title" id="thread-title"
{...field} value={activeThread?.title}
defaultValue={activeThread?.title} onChange={(e) => {
name="title" if (activeThread)
onChange={(e) => field.onChange(e)} updateThreadMetadata({
value={field.value} ...activeThread,
/> title: e.target.value || '',
</FormControl> })
</FormItem> }}
</>
)}
/> />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@ -312,28 +179,22 @@ const Sidebar: React.FC = () => {
> >
Instructions Instructions
</label> </label>
<FormField
key={activeThread?.title}
control={form.control}
name="instructions"
render={({ field }) => (
<>
<FormItem>
<FormControl>
<Textarea <Textarea
id="assistant-instructions" id="assistant-instructions"
placeholder="Eg. You are a helpful assistant." placeholder="Eg. You are a helpful assistant."
{...field} value={activeThread?.assistants[0].instructions ?? ''}
name="instructions" onChange={(e) => {
defaultValue={ if (activeThread)
activeThread?.assistants[0].instructions updateThreadMetadata({
} ...activeThread,
value={field.value} assistants: [
/> {
</FormControl> ...activeThread.assistants[0],
</FormItem> instructions: e.target.value || '',
</> },
)} ],
})
}}
/> />
</div> </div>
{/* Temporary disabled */} {/* Temporary disabled */}
@ -366,7 +227,7 @@ const Sidebar: React.FC = () => {
<div className="mt-6"> <div className="mt-6">
<CardSidebar title="Inference Parameters" asChild> <CardSidebar title="Inference Parameters" asChild>
<div className="p-2"> <div className="p-2">
<ModelSetting form={form} /> <ModelSetting />
</div> </div>
</CardSidebar> </CardSidebar>
</div> </div>
@ -374,11 +235,7 @@ const Sidebar: React.FC = () => {
<div className="mt-4"> <div className="mt-4">
<CardSidebar title="Model Parameters" asChild> <CardSidebar title="Model Parameters" asChild>
<div className="p-2"> <div className="p-2">
{settingComponentBuilder( {settingComponentBuilder(componentDataEngineSetting, true)}
componentDataEngineSetting,
form,
true
)}
</div> </div>
</CardSidebar> </CardSidebar>
</div> </div>
@ -391,38 +248,13 @@ const Sidebar: React.FC = () => {
asChild asChild
> >
<div className="p-2"> <div className="p-2">
<EngineSetting form={form} /> <EngineSetting />
</div> </div>
</CardSidebar> </CardSidebar>
</div> </div>
{Object.keys(form.formState.dirtyFields).length !== 0 && (
<div className="sticky bottom-0 -ml-4 w-[calc(100%+32px)] border-t border-border bg-background px-4 py-3">
<div className="flex gap-3">
<Button themes="secondaryBlue" block onClick={onCancel}>
Cancel
</Button>
<Tooltip open={currentPrompt.length !== 0}>
<TooltipTrigger asChild>
<Button type="submit" block>
{isEngineParamsChanges ? 'Save & Reload' : 'Save'}
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent side="top" className="max-w-[240px]">
<span>{`It seems changes haven't been saved yet`}</span>
<TooltipArrow />
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
</div>
)}
</div> </div>
</CardSidebar> </CardSidebar>
</div> </div>
</form>
</Form>
</div> </div>
) )
} }

View File

@ -30,7 +30,7 @@ import ThreadList from '@/screens/Chat/ThreadList'
import Sidebar, { showRightSideBarAtom } from './Sidebar' import Sidebar, { showRightSideBarAtom } from './Sidebar'
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
import { threadSettingFormUpdateAtom } from '@/helpers/atoms/Thread.atom'
import { import {
activeThreadAtom, activeThreadAtom,
getActiveThreadIdAtom, getActiveThreadIdAtom,
@ -61,8 +61,6 @@ const ChatScreen = () => {
const textareaRef = useRef<HTMLTextAreaElement>(null) const textareaRef = useRef<HTMLTextAreaElement>(null)
const modelRef = useRef(activeModel) const modelRef = useRef(activeModel)
const threadSettingFormUpdate = useAtomValue(threadSettingFormUpdateAtom)
useEffect(() => { useEffect(() => {
modelRef.current = activeModel modelRef.current = activeModel
}, [activeModel]) }, [activeModel])
@ -174,12 +172,7 @@ const ChatScreen = () => {
{messages[messages.length - 1]?.status !== MessageStatus.Pending ? ( {messages[messages.length - 1]?.status !== MessageStatus.Pending ? (
<Button <Button
size="lg" size="lg"
disabled={ disabled={disabled || stateModel.loading || !activeThread}
disabled ||
stateModel.loading ||
!activeThread ||
threadSettingFormUpdate
}
themes="primary" themes="primary"
className="min-w-[100px]" className="min-w-[100px]"
onClick={sendChatMessage} onClick={sendChatMessage}