feat(web): model support json

This commit is contained in:
zhaoying
2026-04-16 19:00:58 +08:00
parent cada860a16
commit e015455fb8
9 changed files with 88 additions and 39 deletions

View File

@@ -2,9 +2,9 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-07 16:49:59 * @Date: 2026-03-07 16:49:59
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-25 11:21:59 * @Last Modified time: 2026-04-16 17:46:02
*/ */
import { useEffect, useState, type FC } from 'react'; import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import { Select, Flex, Space } from 'antd'; import { Select, Flex, Space } from 'antd';
import type { SelectProps } from 'antd/es/select'; import type { SelectProps } from 'antd/es/select';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -14,6 +14,10 @@ import type { Query, Model } from '@/views/ModelManagement/types';
import { getListLogoUrl } from '@/views/ModelManagement/utils'; import { getListLogoUrl } from '@/views/ModelManagement/utils';
import Tag from '@/components/Tag'; import Tag from '@/components/Tag';
export interface ModelSelectRef {
options: Model[];
}
/** Extends AntD SelectProps; omits filterOption since it's handled internally */ /** Extends AntD SelectProps; omits filterOption since it's handled internally */
interface ModelSelectProps extends SelectProps { interface ModelSelectProps extends SelectProps {
/** Extra query params passed to getModelList */ /** Extra query params passed to getModelList */
@@ -24,17 +28,15 @@ interface ModelSelectProps extends SelectProps {
initialData?: Model[]; initialData?: Model[];
} }
const ModelSelect: FC<ModelSelectProps> = ({ const ModelSelect = forwardRef<ModelSelectRef, ModelSelectProps>((
params, { params, placeholder, fontClassName, isAutoFetch = true, initialData = [], ...props },
placeholder, ref
fontClassName, ) => {
isAutoFetch = true,
initialData = [],
...props
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [options, setOptions] = useState<Model[]>([]); const [options, setOptions] = useState<Model[]>([]);
useImperativeHandle(ref, () => ({ options }), [options]);
// Fetch active models whenever params change; stringify for stable deep comparison // Fetch active models whenever params change; stringify for stable deep comparison
useEffect(() => { useEffect(() => {
if (!isAutoFetch) return if (!isAutoFetch) return
@@ -89,6 +91,6 @@ const ModelSelect: FC<ModelSelectProps> = ({
{...props} {...props}
/> />
); );
}; });
export default ModelSelect; export default ModelSelect;

View File

@@ -629,6 +629,7 @@ export const en = {
video: 'Video', video: 'Video',
thinking: 'Deep Thinking', thinking: 'Deep Thinking',
is_thinking: 'Deep Thinking Support', is_thinking: 'Deep Thinking Support',
json_output: 'Support JSON formatted output',
}, },
knowledgeBase: { knowledgeBase: {
home: 'Home', home: 'Home',
@@ -1524,6 +1525,7 @@ export const en = {
}`, }`,
uploadCover: 'Import and Overwrite', uploadCover: 'Import and Overwrite',
refresh: 'Refresh Current Page', refresh: 'Refresh Current Page',
json_output: 'Support JSON formatted output',
}, },
userMemory: { userMemory: {
userMemory: 'User Memory', userMemory: 'User Memory',
@@ -2287,6 +2289,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
messagesPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert', messagesPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert',
vision: 'Vision', vision: 'Vision',
parameterSettings: 'Parameter Settings', parameterSettings: 'Parameter Settings',
json_output: 'Support JSON formatted output',
}, },
start: { start: {
variables: 'Input Fields', variables: 'Input Fields',

View File

@@ -859,6 +859,7 @@ export const zh = {
}`, }`,
uploadCover: '导入并覆盖', uploadCover: '导入并覆盖',
refresh: '刷新当前页', refresh: '刷新当前页',
json_output: '支持JSON格式化输出',
}, },
table: { table: {
totalRecords: '共 {{total}} 条记录' totalRecords: '共 {{total}} 条记录'
@@ -1307,6 +1308,7 @@ export const zh = {
video: '视频', video: '视频',
thinking: '深度思考', thinking: '深度思考',
is_thinking: '支持深度思考', is_thinking: '支持深度思考',
json_output: '支持JSON格式化输出',
}, },
timezones: { timezones: {
'Asia/Shanghai': '中国标准时间 (UTC+8)', 'Asia/Shanghai': '中国标准时间 (UTC+8)',
@@ -2248,6 +2250,7 @@ export const zh = {
messagesPlaceholder: '在此处编写提示,输入“{”插入变量输入“insert”插入', messagesPlaceholder: '在此处编写提示,输入“{”插入变量输入“insert”插入',
vision: '视觉', vision: '视觉',
parameterSettings: '参数设置', parameterSettings: '参数设置',
json_output: '支持JSON格式化输出',
}, },
start: { start: {
variables: '输入字段', variables: '输入字段',

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:28:07 * @Date: 2026-02-03 16:28:07
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 16:56:57 * @Last Modified time: 2026-04-16 18:51:01
*/ */
/** /**
* Model Configuration Modal * Model Configuration Modal
@@ -102,14 +102,15 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
} }
/** Handle model selection change */ /** Handle model selection change */
const handleChange: SelectProps['onChange'] = (_value, option) => { const handleChange: SelectProps['onChange'] = (_value, option) => {
if (source === 'chat') { const newValues: ModelConfig = {
form.setFieldValue('label', (option as Model).name)
}
form.setFieldsValue({
capability: (option as Model).capability, capability: (option as Model).capability,
deep_thinking: false, deep_thinking: false,
}) json_output: false
}
if (source === 'chat') {
newValues.label = (option as Model).name
}
form.setFieldsValue(newValues)
} }
/** Expose methods to parent component */ /** Expose methods to parent component */
@@ -119,12 +120,10 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
})); }));
useEffect(() => { useEffect(() => {
const { deep_thinking: _, ...rest } = data?.model_parameters || {} const { deep_thinking: _, json_output: __, ...rest } = data?.model_parameters || {}
form.setFieldsValue(rest) form.setFieldsValue(rest)
}, [values?.default_model_config_id]) }, [values?.default_model_config_id])
console.log('handleChange values', values)
return ( return (
<RbModal <RbModal
title={t('application.modelConfig')} title={t('application.modelConfig')}
@@ -159,6 +158,9 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
<FormItem name="deep_thinking" valuePropName="checked" hidden={!['model', 'chat'].includes(source) || !(values?.deep_thinking || values?.capability?.includes('thinking'))}> <FormItem name="deep_thinking" valuePropName="checked" hidden={!['model', 'chat'].includes(source) || !(values?.deep_thinking || values?.capability?.includes('thinking'))}>
<Checkbox>{t('application.deep_thinking')}</Checkbox> <Checkbox>{t('application.deep_thinking')}</Checkbox>
</FormItem> </FormItem>
<FormItem name="json_output" valuePropName="checked" hidden={!(values?.capability?.includes('json_output'))}>
<Checkbox>{t('application.json_output')}</Checkbox>
</FormItem>
{source === 'chat' && <FormItem name="label" hidden />} {source === 'chat' && <FormItem name="label" hidden />}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:49 * @Date: 2026-02-03 16:29:49
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 15:46:19 * @Last Modified time: 2026-04-16 18:20:14
*/ */
import type { KnowledgeConfig } from './components/Knowledge/types' import type { KnowledgeConfig } from './components/Knowledge/types'
import type { Variable } from './components/VariableList/types' import type { Variable } from './components/VariableList/types'
@@ -24,20 +24,21 @@ export interface ModelConfig {
default_model_config_id?: string; default_model_config_id?: string;
capability?: Capability[]; capability?: Capability[];
/** Temperature for response randomness (0-2) */ /** Temperature for response randomness (0-2) */
temperature: number; temperature?: number;
/** Maximum tokens in response */ /** Maximum tokens in response */
max_tokens: number; max_tokens?: number;
/** Top-p sampling parameter */ /** Top-p sampling parameter */
top_p: number; top_p?: number;
/** Frequency penalty */ /** Frequency penalty */
frequency_penalty: number; frequency_penalty?: number;
/** Presence penalty */ /** Presence penalty */
presence_penalty: number; presence_penalty?: number;
/** Number of completions to generate */ /** Number of completions to generate */
n: number; n?: number;
/** Stop sequences */ /** Stop sequences */
stop?: string; stop?: string;
deep_thinking?: boolean; deep_thinking?: boolean;
json_output?: boolean;
} }
/** /**

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:49:28 * @Date: 2026-02-03 16:49:28
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 13:56:18 * @Last Modified time: 2026-04-16 18:03:53
*/ */
/** /**
* Custom Model Modal * Custom Model Modal
@@ -14,7 +14,7 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Form, Input, App, Checkbox, Button, Row, Col } from 'antd'; import { Form, Input, App, Checkbox, Button, Row, Col } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps } from '../types'; import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps, Capability } from '../types';
import RbModal from '@/components/RbModal' import RbModal from '@/components/RbModal'
import CustomSelect from '@/components/CustomSelect' import CustomSelect from '@/components/CustomSelect'
import UploadImages from '@/components/Upload/UploadImages' import UploadImages from '@/components/Upload/UploadImages'
@@ -73,6 +73,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
is_video: capability?.includes('video') || false, is_video: capability?.includes('video') || false,
is_audio: capability?.includes('audio') || false, is_audio: capability?.includes('audio') || false,
is_thinking: capability?.includes('thinking') || false, is_thinking: capability?.includes('thinking') || false,
json_output: capability?.includes('json_output') || false,
}); });
} else { } else {
setIsEdit(false); setIsEdit(false);
@@ -102,13 +103,13 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
form form
.validateFields() .validateFields()
.then((values) => { .then((values) => {
const { logo, type, is_vision, is_video, is_audio, is_omni, is_thinking, ...rest } = values; const { logo, type, is_vision, is_video, is_audio, is_omni, is_thinking, json_output, ...rest } = values;
const formData: CustomModelForm = { const formData: CustomModelForm = {
...rest, ...rest,
type, type,
} }
if (!['embedding', 'rerank'].includes(type as string)) { if (!['embedding', 'rerank'].includes(type as string)) {
let capability = is_omni ? ["vision", "audio", 'video'] : [] let capability: Capability[] = is_omni ? ["vision", "audio", 'video'] : []
if (!is_omni) { if (!is_omni) {
if (is_vision) { if (is_vision) {
@@ -124,6 +125,9 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
if (is_thinking) { if (is_thinking) {
capability.push('thinking') capability.push('thinking')
} }
if (json_output) {
capability.push('json_output')
}
formData.capability = capability formData.capability = capability
formData.is_omni = is_omni formData.is_omni = is_omni
@@ -269,6 +273,11 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
<Checkbox>{t('modelNew.is_thinking')}</Checkbox> <Checkbox>{t('modelNew.is_thinking')}</Checkbox>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={24}>
<Form.Item name="json_output" valuePropName="checked" className="rb:mb-0!">
<Checkbox>{t('modelNew.json_output')}</Checkbox>
</Form.Item>
</Col>
</Row> </Row>
} }
</Form> </Form>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:50:18 * @Date: 2026-02-03 16:50:18
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-31 15:48:02 * @Last Modified time: 2026-04-16 18:04:46
*/ */
/** /**
* Type definitions for Model Management * Type definitions for Model Management
@@ -296,6 +296,7 @@ export interface CustomModelForm {
is_audio?: boolean; is_audio?: boolean;
is_omni?: boolean; is_omni?: boolean;
is_thinking?: boolean; is_thinking?: boolean;
json_output?: boolean;
capability?: Capability[]; capability?: Capability[];
} }
@@ -325,7 +326,7 @@ export interface BaseRef {
modelListDetailRefresh?: () => void; modelListDetailRefresh?: () => void;
} }
export type Capability = 'vision' | 'audio' | 'video' | 'thinking'; export type Capability = 'vision' | 'audio' | 'video' | 'thinking' | 'json_output';
export interface Model { export interface Model {
name: string; name: string;
type: string; type: string;

View File

@@ -1,15 +1,28 @@
import { type FC } from "react"; import { type FC, useEffect, useRef, useState } from "react";
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Form } from 'antd' import { Form, Switch } from 'antd'
import RbSlider from '@/components/RbSlider' import RbSlider from '@/components/RbSlider'
import RbCard from '@/components/RbCard/Card' import RbCard from '@/components/RbCard/Card'
import ModelSelect from '@/components/ModelSelect' import ModelSelect, { type ModelSelectRef } from '@/components/ModelSelect'
import type { Model } from '@/views/ModelManagement/types';
const ModelConfig: FC = () => { const ModelConfig: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const form = Form.useFormInstance() const form = Form.useFormInstance()
const model_id = Form.useWatch(['model_id'], form) const model_id = Form.useWatch(['model_id'], form)
const modelSelectRef = useRef<ModelSelectRef>(null)
const [selectedModel, setSelectedModel] = useState<Model | null>(null)
useEffect(() => {
if (model_id && modelSelectRef.current?.options) {
const model = modelSelectRef.current?.options.find(item => item.id === model_id)
setSelectedModel(model || null)
form.setFieldValue('json_output', false)
} else {
setSelectedModel(null)
}
}, [model_id, modelSelectRef.current?.options])
console.log('ModelConfig', model_id) console.log('ModelConfig', model_id)
return ( return (
@@ -21,6 +34,7 @@ const ModelConfig: FC = () => {
required required
> >
<ModelSelect <ModelSelect
ref={modelSelectRef}
placeholder={t('common.pleaseSelect')} placeholder={t('common.pleaseSelect')}
params={{ type: 'llm,chat' }} params={{ type: 'llm,chat' }}
className="rb:w-full!" className="rb:w-full!"
@@ -52,7 +66,7 @@ const ModelConfig: FC = () => {
<Form.Item <Form.Item
name="max_tokens" name="max_tokens"
label={t('workflow.config.llm.max_tokens')} label={t('workflow.config.llm.max_tokens')}
className="rb:mb-0!" className="rb:mb-1.5!"
> >
<RbSlider <RbSlider
min={256} min={256}
@@ -63,6 +77,16 @@ const ModelConfig: FC = () => {
className="rb:-mt-2!" className="rb:-mt-2!"
/> />
</Form.Item> </Form.Item>
<Form.Item
name="json_output"
valuePropName="checked"
label={t('workflow.config.llm.json_output')}
layout="horizontal"
className="rb:mb-0!"
hidden={!(selectedModel?.capability?.includes('json_output'))}
>
<Switch />
</Form.Item>
</RbCard> </RbCard>
)} )}
</> </>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18 * @Date: 2026-02-03 15:06:18
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 19:56:56 * @Last Modified time: 2026-04-16 17:52:30
*/ */
import LoopNode from './components/Nodes/LoopNode'; import LoopNode from './components/Nodes/LoopNode';
import NormalNode from './components/Nodes/NormalNode'; import NormalNode from './components/Nodes/NormalNode';
@@ -101,6 +101,10 @@ export const nodeLibrary: NodeLibrary[] = [
step: 1, step: 1,
defaultValue: 2000 defaultValue: 2000
}, },
json_output: {
type: 'define',
defaultValue: false
},
context: { context: {
type: 'variableList', type: 'variableList',
placeholder: 'workflow.config.llm.contextPlaceholder' placeholder: 'workflow.config.llm.contextPlaceholder'