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 ControllerType = 'slider' | 'checkbox' | 'input'
|
||||
export type ControllerType = 'slider' | 'checkbox' | 'input' | 'tag'
|
||||
|
||||
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
||||
|
||||
@ -22,7 +22,7 @@ export type InputAction = InputActionsTuple[number]
|
||||
|
||||
export type InputComponentProps = {
|
||||
placeholder: string
|
||||
value: string
|
||||
value: string | string[]
|
||||
type?: InputType
|
||||
textAlign?: 'left' | 'right'
|
||||
inputActions?: InputAction[]
|
||||
|
||||
@ -4,7 +4,10 @@ import SettingComponentBuilder from '@/containers/ModelSetting/SettingComponent'
|
||||
|
||||
type Props = {
|
||||
componentData: SettingComponentProps[]
|
||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
||||
onValueChanged: (
|
||||
key: string,
|
||||
value: string | number | boolean | string[]
|
||||
) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
|
||||
@ -19,28 +19,30 @@ const ModelConfigInput = ({
|
||||
description,
|
||||
placeholder,
|
||||
onValueChanged,
|
||||
}: Props) => (
|
||||
<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}
|
||||
}: Props) => {
|
||||
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>
|
||||
<TextArea
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onValueChanged?.(e.target.value)}
|
||||
autoResize
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<TextArea
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onValueChanged?.(e.target.value)}
|
||||
autoResize
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelConfigInput
|
||||
|
||||
@ -8,11 +8,15 @@ import {
|
||||
import Checkbox from '@/containers/Checkbox'
|
||||
import ModelConfigInput from '@/containers/ModelConfigInput'
|
||||
import SliderRightPanel from '@/containers/SliderRightPanel'
|
||||
import TagInput from '@/containers/TagInput'
|
||||
|
||||
type Props = {
|
||||
componentProps: SettingComponentProps[]
|
||||
disabled?: boolean
|
||||
onValueUpdated: (key: string, value: string | number | boolean) => void
|
||||
onValueUpdated: (
|
||||
key: string,
|
||||
value: string | number | boolean | string[]
|
||||
) => void
|
||||
}
|
||||
|
||||
const SettingComponent: React.FC<Props> = ({
|
||||
@ -53,7 +57,24 @@ const SettingComponent: React.FC<Props> = ({
|
||||
name={data.key}
|
||||
description={data.description}
|
||||
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)}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -6,7 +6,10 @@ import SettingComponentBuilder from './SettingComponent'
|
||||
|
||||
type Props = {
|
||||
componentProps: SettingComponentProps[]
|
||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
||||
onValueChanged: (
|
||||
key: string,
|
||||
value: string | number | boolean | string[]
|
||||
) => void
|
||||
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])
|
||||
|
||||
const onValueChanged = useCallback(
|
||||
(key: string, value: string | number | boolean) => {
|
||||
(key: string, value: string | number | boolean | string[]) => {
|
||||
setCurrentModelSettingParams((prevParams) => ({
|
||||
...prevParams,
|
||||
[key]: value,
|
||||
|
||||
@ -44,7 +44,7 @@ const ExtensionSetting = () => {
|
||||
|
||||
const onValueChanged = async (
|
||||
key: string,
|
||||
value: string | number | boolean
|
||||
value: string | number | boolean | string[]
|
||||
) => {
|
||||
// find the key in settings state, update it and set the state back
|
||||
const newSettings = settings.map((setting) => {
|
||||
|
||||
@ -51,7 +51,7 @@ const SettingDetailTextInputItem = ({
|
||||
}, [])
|
||||
|
||||
const copy = useCallback(() => {
|
||||
navigator.clipboard.writeText(value)
|
||||
navigator.clipboard.writeText(value as string)
|
||||
if (value.length > 0) {
|
||||
setCopied(true)
|
||||
}
|
||||
|
||||
@ -5,7 +5,10 @@ import SettingDetailToggleItem from './SettingDetailToggleItem'
|
||||
|
||||
type Props = {
|
||||
componentProps: SettingComponentProps[]
|
||||
onValueUpdated: (key: string, value: string | number | boolean) => void
|
||||
onValueUpdated: (
|
||||
key: string,
|
||||
value: string | number | boolean | string[]
|
||||
) => void
|
||||
}
|
||||
|
||||
const SettingDetailItem = ({ componentProps, onValueUpdated }: Props) => {
|
||||
|
||||
@ -24,7 +24,7 @@ const AssistantSetting: React.FC<Props> = ({ componentData }) => {
|
||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||
|
||||
const onValueChanged = useCallback(
|
||||
(key: string, value: string | number | boolean) => {
|
||||
(key: string, value: string | number | boolean | string[]) => {
|
||||
if (!activeThread) return
|
||||
const shouldReloadModel =
|
||||
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
||||
|
||||
@ -26,7 +26,7 @@ const PromptTemplateSetting: React.FC<Props> = ({ componentData }) => {
|
||||
|
||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||
const onValueChanged = useCallback(
|
||||
(key: string, value: string | number | boolean) => {
|
||||
(key: string, value: string | number | boolean | string[]) => {
|
||||
if (!activeThread) return
|
||||
|
||||
setEngineParamsUpdate(true)
|
||||
|
||||
@ -173,7 +173,7 @@ const ThreadRightPanel = () => {
|
||||
}, 300)
|
||||
|
||||
const onValueChanged = useCallback(
|
||||
(key: string, value: string | number | boolean) => {
|
||||
(key: string, value: string | number | boolean | string[]) => {
|
||||
if (!activeThread) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -60,11 +60,14 @@ export const getConfigurationsData = (
|
||||
componentSetting.controllerProps.placeholder = placeholder
|
||||
} else if ('checkbox' === componentSetting.controllerType) {
|
||||
const checked = keySetting as boolean
|
||||
|
||||
if ('value' in componentSetting.controllerProps)
|
||||
componentSetting.controllerProps.value = checked
|
||||
} else if ('tag' === componentSetting.controllerType) {
|
||||
if ('value' in componentSetting.controllerProps)
|
||||
componentSetting.controllerProps.value = keySetting as string
|
||||
}
|
||||
componentData.push(componentSetting)
|
||||
})
|
||||
|
||||
return componentData
|
||||
}
|
||||
|
||||
@ -17,10 +17,10 @@ export const presetConfiguration: Record<string, SettingComponentProps> = {
|
||||
key: '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.`,
|
||||
controllerType: 'input',
|
||||
controllerType: 'tag',
|
||||
controllerProps: {
|
||||
placeholder: 'Stop',
|
||||
value: '',
|
||||
placeholder: 'Enter stop words',
|
||||
value: [''],
|
||||
},
|
||||
requireModelReload: false,
|
||||
configType: 'runtime',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user