feat: stop word model setting (#4113)
* feat: stop word model setting * chore: update test tag input * chore: handle UI when no stop word * chore: fix types of value tag input
This commit is contained in:
parent
a5acaf0556
commit
314cb03693
@ -12,7 +12,7 @@ export type SettingComponentProps = {
|
|||||||
|
|
||||||
export type ConfigType = 'runtime' | 'setting'
|
export type ConfigType = 'runtime' | 'setting'
|
||||||
|
|
||||||
export type ControllerType = 'slider' | 'checkbox' | 'input'
|
export type ControllerType = 'slider' | 'checkbox' | 'input' | 'tag'
|
||||||
|
|
||||||
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export type InputAction = InputActionsTuple[number]
|
|||||||
|
|
||||||
export type InputComponentProps = {
|
export type InputComponentProps = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
value: string
|
value: string | string[]
|
||||||
type?: InputType
|
type?: InputType
|
||||||
textAlign?: 'left' | 'right'
|
textAlign?: 'left' | 'right'
|
||||||
inputActions?: InputAction[]
|
inputActions?: InputAction[]
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import SettingComponentBuilder from '@/containers/ModelSetting/SettingComponent'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
componentData: SettingComponentProps[]
|
componentData: SettingComponentProps[]
|
||||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
onValueChanged: (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean | string[]
|
||||||
|
) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,8 @@ const ModelConfigInput = ({
|
|||||||
description,
|
description,
|
||||||
placeholder,
|
placeholder,
|
||||||
onValueChanged,
|
onValueChanged,
|
||||||
}: Props) => (
|
}: Props) => {
|
||||||
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-2 flex items-center gap-x-2">
|
<div className="mb-2 flex items-center gap-x-2">
|
||||||
<p className="font-medium">{title}</p>
|
<p className="font-medium">{title}</p>
|
||||||
@ -41,6 +42,7 @@ const ModelConfigInput = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ModelConfigInput
|
export default ModelConfigInput
|
||||||
|
|||||||
@ -8,11 +8,15 @@ import {
|
|||||||
import Checkbox from '@/containers/Checkbox'
|
import Checkbox from '@/containers/Checkbox'
|
||||||
import ModelConfigInput from '@/containers/ModelConfigInput'
|
import ModelConfigInput from '@/containers/ModelConfigInput'
|
||||||
import SliderRightPanel from '@/containers/SliderRightPanel'
|
import SliderRightPanel from '@/containers/SliderRightPanel'
|
||||||
|
import TagInput from '@/containers/TagInput'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
componentProps: SettingComponentProps[]
|
componentProps: SettingComponentProps[]
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onValueUpdated: (key: string, value: string | number | boolean) => void
|
onValueUpdated: (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean | string[]
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingComponent: React.FC<Props> = ({
|
const SettingComponent: React.FC<Props> = ({
|
||||||
@ -53,7 +57,24 @@ const SettingComponent: React.FC<Props> = ({
|
|||||||
name={data.key}
|
name={data.key}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={textValue}
|
value={textValue as string}
|
||||||
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'tag': {
|
||||||
|
const { placeholder, value: textValue } =
|
||||||
|
data.controllerProps as InputComponentProps
|
||||||
|
return (
|
||||||
|
<TagInput
|
||||||
|
title={data.title}
|
||||||
|
disabled={disabled}
|
||||||
|
key={data.key}
|
||||||
|
name={data.key}
|
||||||
|
description={data.description}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={textValue as string[]}
|
||||||
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -6,7 +6,10 @@ import SettingComponentBuilder from './SettingComponent'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
componentProps: SettingComponentProps[]
|
componentProps: SettingComponentProps[]
|
||||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
onValueChanged: (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean | string[]
|
||||||
|
) => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
50
web/containers/TagInput/index.test.tsx
Normal file
50
web/containers/TagInput/index.test.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import TagInput from './index' // Adjust the import path as necessary
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
describe('TagInput Component', () => {
|
||||||
|
let props: any
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
props = {
|
||||||
|
title: 'Tags',
|
||||||
|
name: 'tag-input',
|
||||||
|
description: 'Add your tags',
|
||||||
|
placeholder: 'Enter a tag',
|
||||||
|
value: ['tag1', 'tag2'],
|
||||||
|
onValueChanged: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { getByText, getByPlaceholderText } = render(<TagInput {...props} />)
|
||||||
|
expect(getByText('Tags')).toBeInTheDocument()
|
||||||
|
expect(getByText('tag1')).toBeInTheDocument()
|
||||||
|
expect(getByText('tag2')).toBeInTheDocument()
|
||||||
|
expect(getByPlaceholderText('Enter a tag')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onValueChanged when a new tag is added', () => {
|
||||||
|
const { getByPlaceholderText } = render(<TagInput {...props} />)
|
||||||
|
const input = getByPlaceholderText('Enter a tag')
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: 'tag3' } })
|
||||||
|
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' })
|
||||||
|
|
||||||
|
expect(props.onValueChanged).toHaveBeenCalledWith(
|
||||||
|
expect.arrayContaining(['tag1', 'tag2', 'tag3'])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onValueChanged when a tag is removed', () => {
|
||||||
|
const { getAllByRole } = render(<TagInput {...props} />)
|
||||||
|
const removeButton = getAllByRole('button')[0] // Click on the first remove button
|
||||||
|
|
||||||
|
fireEvent.click(removeButton)
|
||||||
|
|
||||||
|
expect(props.onValueChanged).toHaveBeenCalledWith(
|
||||||
|
expect.arrayContaining(['tag2'])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
85
web/containers/TagInput/index.tsx
Normal file
85
web/containers/TagInput/index.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { Badge, Input, Tooltip } from '@janhq/joi'
|
||||||
|
|
||||||
|
import { InfoIcon, XIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string
|
||||||
|
disabled?: boolean
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
placeholder: string
|
||||||
|
value: string[]
|
||||||
|
onValueChanged?: (e: string | number | boolean | string[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagInput = ({
|
||||||
|
title,
|
||||||
|
disabled = false,
|
||||||
|
value,
|
||||||
|
description,
|
||||||
|
placeholder,
|
||||||
|
onValueChanged,
|
||||||
|
}: Props) => {
|
||||||
|
const [pendingDataPoint, setPendingDataPoint] = useState('')
|
||||||
|
|
||||||
|
const addPendingDataPoint = () => {
|
||||||
|
if (pendingDataPoint) {
|
||||||
|
const newDataPoints = new Set([...value, pendingDataPoint])
|
||||||
|
onValueChanged && onValueChanged(Array.from(newDataPoints))
|
||||||
|
setPendingDataPoint('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="mb-2 flex items-center gap-x-2">
|
||||||
|
<p className="font-medium">{title}</p>
|
||||||
|
<Tooltip
|
||||||
|
trigger={
|
||||||
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="flex-shrink-0 text-[hsla(var(--text-secondary))]"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
content={description}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={pendingDataPoint}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(e) => setPendingDataPoint(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="w-full"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === 'Tab') {
|
||||||
|
e.preventDefault()
|
||||||
|
addPendingDataPoint()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{value.length > 0 && (
|
||||||
|
<div className="mt-2 flex min-h-[2.5rem] flex-wrap items-center gap-2 overflow-y-auto">
|
||||||
|
{value.map((item, idx) => (
|
||||||
|
<Badge key={idx} theme="secondary">
|
||||||
|
{item}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ml-1.5 w-3 bg-transparent"
|
||||||
|
onClick={() => {
|
||||||
|
onValueChanged &&
|
||||||
|
onValueChanged(value.filter((i) => i !== item))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<XIcon className="w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TagInput
|
||||||
@ -86,7 +86,7 @@ const LocalServerRightPanel = () => {
|
|||||||
}, [currentModelSettingParams, setLocalAPIserverModelParams])
|
}, [currentModelSettingParams, setLocalAPIserverModelParams])
|
||||||
|
|
||||||
const onValueChanged = useCallback(
|
const onValueChanged = useCallback(
|
||||||
(key: string, value: string | number | boolean) => {
|
(key: string, value: string | number | boolean | string[]) => {
|
||||||
setCurrentModelSettingParams((prevParams) => ({
|
setCurrentModelSettingParams((prevParams) => ({
|
||||||
...prevParams,
|
...prevParams,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const ExtensionSetting = () => {
|
|||||||
|
|
||||||
const onValueChanged = async (
|
const onValueChanged = async (
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean
|
value: string | number | boolean | string[]
|
||||||
) => {
|
) => {
|
||||||
// find the key in settings state, update it and set the state back
|
// find the key in settings state, update it and set the state back
|
||||||
const newSettings = settings.map((setting) => {
|
const newSettings = settings.map((setting) => {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ const SettingDetailTextInputItem = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const copy = useCallback(() => {
|
const copy = useCallback(() => {
|
||||||
navigator.clipboard.writeText(value)
|
navigator.clipboard.writeText(value as string)
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,10 @@ import SettingDetailToggleItem from './SettingDetailToggleItem'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
componentProps: SettingComponentProps[]
|
componentProps: SettingComponentProps[]
|
||||||
onValueUpdated: (key: string, value: string | number | boolean) => void
|
onValueUpdated: (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean | string[]
|
||||||
|
) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingDetailItem = ({ componentProps, onValueUpdated }: Props) => {
|
const SettingDetailItem = ({ componentProps, onValueUpdated }: Props) => {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const AssistantSetting: React.FC<Props> = ({ componentData }) => {
|
|||||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
|
||||||
const onValueChanged = useCallback(
|
const onValueChanged = useCallback(
|
||||||
(key: string, value: string | number | boolean) => {
|
(key: string, value: string | number | boolean | string[]) => {
|
||||||
if (!activeThread) return
|
if (!activeThread) return
|
||||||
const shouldReloadModel =
|
const shouldReloadModel =
|
||||||
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const PromptTemplateSetting: React.FC<Props> = ({ componentData }) => {
|
|||||||
|
|
||||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
const onValueChanged = useCallback(
|
const onValueChanged = useCallback(
|
||||||
(key: string, value: string | number | boolean) => {
|
(key: string, value: string | number | boolean | string[]) => {
|
||||||
if (!activeThread) return
|
if (!activeThread) return
|
||||||
|
|
||||||
setEngineParamsUpdate(true)
|
setEngineParamsUpdate(true)
|
||||||
|
|||||||
@ -173,7 +173,7 @@ const ThreadRightPanel = () => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
|
|
||||||
const onValueChanged = useCallback(
|
const onValueChanged = useCallback(
|
||||||
(key: string, value: string | number | boolean) => {
|
(key: string, value: string | number | boolean | string[]) => {
|
||||||
if (!activeThread) {
|
if (!activeThread) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,11 +60,14 @@ export const getConfigurationsData = (
|
|||||||
componentSetting.controllerProps.placeholder = placeholder
|
componentSetting.controllerProps.placeholder = placeholder
|
||||||
} else if ('checkbox' === componentSetting.controllerType) {
|
} else if ('checkbox' === componentSetting.controllerType) {
|
||||||
const checked = keySetting as boolean
|
const checked = keySetting as boolean
|
||||||
|
|
||||||
if ('value' in componentSetting.controllerProps)
|
if ('value' in componentSetting.controllerProps)
|
||||||
componentSetting.controllerProps.value = checked
|
componentSetting.controllerProps.value = checked
|
||||||
|
} else if ('tag' === componentSetting.controllerType) {
|
||||||
|
if ('value' in componentSetting.controllerProps)
|
||||||
|
componentSetting.controllerProps.value = keySetting as string
|
||||||
}
|
}
|
||||||
componentData.push(componentSetting)
|
componentData.push(componentSetting)
|
||||||
})
|
})
|
||||||
|
|
||||||
return componentData
|
return componentData
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,10 +17,10 @@ export const presetConfiguration: Record<string, SettingComponentProps> = {
|
|||||||
key: 'stop',
|
key: 'stop',
|
||||||
title: 'Stop',
|
title: 'Stop',
|
||||||
description: `Defines specific tokens or phrases that signal the model to stop producing further output, allowing you to control the length and coherence of the output.`,
|
description: `Defines specific tokens or phrases that signal the model to stop producing further output, allowing you to control the length and coherence of the output.`,
|
||||||
controllerType: 'input',
|
controllerType: 'tag',
|
||||||
controllerProps: {
|
controllerProps: {
|
||||||
placeholder: 'Stop',
|
placeholder: 'Enter stop words',
|
||||||
value: '',
|
value: [''],
|
||||||
},
|
},
|
||||||
requireModelReload: false,
|
requireModelReload: false,
|
||||||
configType: 'runtime',
|
configType: 'runtime',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user