diff --git a/core/src/types/setting/settingComponent.ts b/core/src/types/setting/settingComponent.ts
index 2eae4e16f..2474f6bd4 100644
--- a/core/src/types/setting/settingComponent.ts
+++ b/core/src/types/setting/settingComponent.ts
@@ -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[]
diff --git a/web/containers/EngineSetting/index.tsx b/web/containers/EngineSetting/index.tsx
index acbd507ce..0ae2929bf 100644
--- a/web/containers/EngineSetting/index.tsx
+++ b/web/containers/EngineSetting/index.tsx
@@ -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
}
diff --git a/web/containers/ModelConfigInput/index.tsx b/web/containers/ModelConfigInput/index.tsx
index f0e6ea1f2..e67080df2 100644
--- a/web/containers/ModelConfigInput/index.tsx
+++ b/web/containers/ModelConfigInput/index.tsx
@@ -19,28 +19,30 @@ const ModelConfigInput = ({
description,
placeholder,
onValueChanged,
-}: Props) => (
-
-
-
{title}
-
- }
- content={description}
+}: Props) => {
+ return (
+
+
+
{title}
+
+ }
+ content={description}
+ />
+
+
-
-)
+ )
+}
export default ModelConfigInput
diff --git a/web/containers/ModelSetting/SettingComponent.tsx b/web/containers/ModelSetting/SettingComponent.tsx
index ac45b0f06..d892dbe61 100644
--- a/web/containers/ModelSetting/SettingComponent.tsx
+++ b/web/containers/ModelSetting/SettingComponent.tsx
@@ -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
= ({
@@ -53,7 +57,24 @@ const SettingComponent: React.FC = ({
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 (
+ onValueUpdated(data.key, value)}
/>
)
diff --git a/web/containers/ModelSetting/index.tsx b/web/containers/ModelSetting/index.tsx
index 27559f065..9c21bf6ac 100644
--- a/web/containers/ModelSetting/index.tsx
+++ b/web/containers/ModelSetting/index.tsx
@@ -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
}
diff --git a/web/containers/TagInput/index.test.tsx b/web/containers/TagInput/index.test.tsx
new file mode 100644
index 000000000..e3d6ef8cc
--- /dev/null
+++ b/web/containers/TagInput/index.test.tsx
@@ -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()
+ 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()
+ 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()
+ const removeButton = getAllByRole('button')[0] // Click on the first remove button
+
+ fireEvent.click(removeButton)
+
+ expect(props.onValueChanged).toHaveBeenCalledWith(
+ expect.arrayContaining(['tag2'])
+ )
+ })
+})
diff --git a/web/containers/TagInput/index.tsx b/web/containers/TagInput/index.tsx
new file mode 100644
index 000000000..d45fafee5
--- /dev/null
+++ b/web/containers/TagInput/index.tsx
@@ -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 (
+
+
+
{title}
+
+ }
+ content={description}
+ />
+
+
setPendingDataPoint(e.target.value)}
+ placeholder={placeholder}
+ className="w-full"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === 'Tab') {
+ e.preventDefault()
+ addPendingDataPoint()
+ }
+ }}
+ />
+ {value.length > 0 && (
+
+ {value.map((item, idx) => (
+
+ {item}
+
+
+ ))}
+
+ )}
+
+ )
+}
+
+export default TagInput
diff --git a/web/screens/LocalServer/LocalServerRightPanel/index.tsx b/web/screens/LocalServer/LocalServerRightPanel/index.tsx
index 5dba251df..900a8128e 100644
--- a/web/screens/LocalServer/LocalServerRightPanel/index.tsx
+++ b/web/screens/LocalServer/LocalServerRightPanel/index.tsx
@@ -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,
diff --git a/web/screens/Settings/ExtensionSetting/index.tsx b/web/screens/Settings/ExtensionSetting/index.tsx
index 4a8b140f3..c8e80a6de 100644
--- a/web/screens/Settings/ExtensionSetting/index.tsx
+++ b/web/screens/Settings/ExtensionSetting/index.tsx
@@ -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) => {
diff --git a/web/screens/Settings/SettingDetail/SettingDetailItem/SettingDetailTextInputItem/index.tsx b/web/screens/Settings/SettingDetail/SettingDetailItem/SettingDetailTextInputItem/index.tsx
index 73bd18f50..c4077a563 100644
--- a/web/screens/Settings/SettingDetail/SettingDetailItem/SettingDetailTextInputItem/index.tsx
+++ b/web/screens/Settings/SettingDetail/SettingDetailItem/SettingDetailTextInputItem/index.tsx
@@ -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)
}
diff --git a/web/screens/Settings/SettingDetail/SettingDetailItem/index.tsx b/web/screens/Settings/SettingDetail/SettingDetailItem/index.tsx
index a406a055f..7c44095c8 100644
--- a/web/screens/Settings/SettingDetail/SettingDetailItem/index.tsx
+++ b/web/screens/Settings/SettingDetail/SettingDetailItem/index.tsx
@@ -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) => {
diff --git a/web/screens/Thread/ThreadCenterPanel/AssistantSetting/index.tsx b/web/screens/Thread/ThreadCenterPanel/AssistantSetting/index.tsx
index f73efb486..95c905dde 100644
--- a/web/screens/Thread/ThreadCenterPanel/AssistantSetting/index.tsx
+++ b/web/screens/Thread/ThreadCenterPanel/AssistantSetting/index.tsx
@@ -24,7 +24,7 @@ const AssistantSetting: React.FC = ({ 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
diff --git a/web/screens/Thread/ThreadRightPanel/PromptTemplateSetting/index.tsx b/web/screens/Thread/ThreadRightPanel/PromptTemplateSetting/index.tsx
index e53e7fd5f..fad7fd09c 100644
--- a/web/screens/Thread/ThreadRightPanel/PromptTemplateSetting/index.tsx
+++ b/web/screens/Thread/ThreadRightPanel/PromptTemplateSetting/index.tsx
@@ -26,7 +26,7 @@ const PromptTemplateSetting: React.FC = ({ 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)
diff --git a/web/screens/Thread/ThreadRightPanel/index.tsx b/web/screens/Thread/ThreadRightPanel/index.tsx
index 674c97766..952ba8eb3 100644
--- a/web/screens/Thread/ThreadRightPanel/index.tsx
+++ b/web/screens/Thread/ThreadRightPanel/index.tsx
@@ -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
}
diff --git a/web/utils/componentSettings.ts b/web/utils/componentSettings.ts
index ade558c95..a961d36da 100644
--- a/web/utils/componentSettings.ts
+++ b/web/utils/componentSettings.ts
@@ -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
}
diff --git a/web/utils/predefinedComponent.ts b/web/utils/predefinedComponent.ts
index 3a9f45e92..daa8ed345 100644
--- a/web/utils/predefinedComponent.ts
+++ b/web/utils/predefinedComponent.ts
@@ -17,10 +17,10 @@ export const presetConfiguration: Record = {
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',