fix: 对齐智能体模型新接口并放宽登录手机号校验
parent
46ddf0652a
commit
409bbcc297
|
|
@ -73,13 +73,24 @@ export interface ExternalToolPlugin extends ExternalToolPluginPayload {
|
|||
isTemp?: boolean;
|
||||
}
|
||||
|
||||
export interface AgentModelConfig {
|
||||
model: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
role: string;
|
||||
priority: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
avatar: string;
|
||||
prompt: string;
|
||||
model: string;
|
||||
model?: string;
|
||||
models?: AgentModelConfig[];
|
||||
temperature: number;
|
||||
owner_id?: string | null;
|
||||
team_id?: string | null;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
ToolCallTrace
|
||||
} from '../api';
|
||||
import Markdown from './Markdown';
|
||||
import { parseAgentModels } from '../pages/chat/utils/agentModels';
|
||||
|
||||
interface Props {
|
||||
agent: Agent;
|
||||
|
|
@ -74,8 +75,9 @@ export default function ChatPreview({ agent, agentId }: Props) {
|
|||
sid = created.id;
|
||||
setSessionId(sid);
|
||||
}
|
||||
const model = String(agent?.model || '').split(',')[0]?.trim() || undefined;
|
||||
const modelId = model ? undefined : undefined;
|
||||
const agentModels = parseAgentModels(agent?.models ?? agent?.model);
|
||||
const model = agentModels[0]?.name || undefined;
|
||||
const modelId = agentModels[0]?.id || undefined;
|
||||
const targetAgentId = agentId;
|
||||
await streamChat(
|
||||
sid,
|
||||
|
|
|
|||
|
|
@ -51,20 +51,33 @@ export const TYPE_TAG: Record<SkillType, { color: string; icon: string; label: s
|
|||
|
||||
export const isImageUrl = (url: string | undefined) => url?.startsWith('http') || url?.startsWith('/');
|
||||
|
||||
export const parseModelSelections = (value?: string | string[]) => {
|
||||
// parseModelSelectionItem 将多种模型结构统一提取为模型 ID。
|
||||
const parseModelSelectionItem = (item: any) => {
|
||||
if (!item) {
|
||||
return '';
|
||||
}
|
||||
if (typeof item === 'string') {
|
||||
return item;
|
||||
}
|
||||
if (typeof item === 'object' && item.model?.id) {
|
||||
return String(item.model.id);
|
||||
}
|
||||
if (typeof item === 'object' && item.id) {
|
||||
return String(item.id);
|
||||
}
|
||||
return String(item);
|
||||
};
|
||||
|
||||
// parseModelSelections 负责把接口返回的模型配置统一转换为模型 ID 列表。
|
||||
export const parseModelSelections = (value?: unknown) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value;
|
||||
return value.map(parseModelSelectionItem).filter(Boolean);
|
||||
}
|
||||
// 尝试解析 JSON 格式
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '[]'));
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map((item: any) => {
|
||||
if (typeof item === 'object' && item.id) {
|
||||
return item.id;
|
||||
}
|
||||
return String(item);
|
||||
}).filter(Boolean);
|
||||
return parsed.map(parseModelSelectionItem).filter(Boolean);
|
||||
}
|
||||
} catch {
|
||||
// 兼容旧格式:逗号分隔的字符串
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormInstance } from 'antd';
|
||||
import { Agent, AgentAPI, Team, TeamAPI, AiModel, ModelAPI, ImageAPI } from '../../../api';
|
||||
import { DEFAULT_AVATAR } from '../constants';
|
||||
import { Agent, AgentAPI, AgentModelConfig, Team, TeamAPI, AiModel, ModelAPI, ImageAPI } from '../../../api';
|
||||
import { DEFAULT_AVATAR, parseModelSelections } from '../constants';
|
||||
|
||||
interface UseAgentEditorOptions {
|
||||
id?: string;
|
||||
|
|
@ -11,6 +11,31 @@ interface UseAgentEditorOptions {
|
|||
navigate: any;
|
||||
}
|
||||
|
||||
// buildAgentModelsPayload 将表单里的单选模型值转换为后端需要的 models 数组结构。
|
||||
const buildAgentModelsPayload = (modelId: string | undefined, models: AiModel[]): AgentModelConfig[] => {
|
||||
if (!modelId) {
|
||||
return [];
|
||||
}
|
||||
const selectedModel = models.find((item) => item.id === modelId);
|
||||
return [
|
||||
{
|
||||
model: {
|
||||
id: modelId,
|
||||
name: selectedModel?.model_name || '',
|
||||
},
|
||||
role: 'primary',
|
||||
priority: 1,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// normalizeAgentFormValues 将接口返回的 models 字段映射为表单使用的 model 单值。
|
||||
const normalizeAgentFormValues = (data: Agent) => ({
|
||||
...data,
|
||||
model: parseModelSelections(data.models)[0] || '',
|
||||
});
|
||||
|
||||
export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentEditorOptions) {
|
||||
const [agent, setAgent] = useState<Agent | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
|
@ -85,7 +110,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
|
|||
setAgent(data);
|
||||
if (force || autoSaveStatus !== 'dirty') {
|
||||
hydratingRef.current = true;
|
||||
form.setFieldsValue(data);
|
||||
form.setFieldsValue(normalizeAgentFormValues(data));
|
||||
window.setTimeout(() => {
|
||||
hydratingRef.current = false;
|
||||
}, 0);
|
||||
|
|
@ -163,14 +188,15 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
|
|||
Object.keys(values).forEach((key) => {
|
||||
const formValue = (values as any)[key];
|
||||
const originalValue = (agent as any)?.[key];
|
||||
// 特殊处理 model 字段:将 id 和 name 组合成 JSON 数组字符串
|
||||
if (key === 'model' && Array.isArray(formValue)) {
|
||||
const modelObjects = formValue.map((modelId: string) => {
|
||||
const model = models.find((m) => m.id === modelId);
|
||||
return { id: modelId, name: model?.model_name || '' };
|
||||
});
|
||||
changedFields[key] = JSON.stringify(modelObjects);
|
||||
} else if (formValue !== originalValue) {
|
||||
// model 字段在表单内是单值,提交给后端时需要转换为新的 models 数组结构。
|
||||
if (key === 'model') {
|
||||
const nextModels = buildAgentModelsPayload(formValue, models);
|
||||
if (JSON.stringify(nextModels) !== JSON.stringify(agent?.models || [])) {
|
||||
changedFields.models = nextModels;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (formValue !== originalValue) {
|
||||
changedFields[key] = formValue;
|
||||
}
|
||||
});
|
||||
|
|
@ -181,7 +207,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
|
|||
}
|
||||
const updatedAgent = await AgentAPI.update(id!, changedFields);
|
||||
setAgent(updatedAgent);
|
||||
form.setFieldsValue(updatedAgent);
|
||||
form.setFieldsValue(normalizeAgentFormValues(updatedAgent));
|
||||
if (!silent) message.success('已保存');
|
||||
setAutoSaveStatus('saved');
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ export default function LoginFormCard({ logic }: { logic: LoginPageLogic }) {
|
|||
label="手机号"
|
||||
rules={[
|
||||
{ required: true, message: '请填写手机号' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式' }
|
||||
]}
|
||||
>
|
||||
<Input placeholder="13800138000" size="large" autoFocus />
|
||||
|
|
|
|||
|
|
@ -2,37 +2,26 @@ import { useState, useEffect } from 'react';
|
|||
import { ApiOutlined, DeleteOutlined, EditOutlined, SettingOutlined, EllipsisOutlined, HistoryOutlined, LogoutOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, Modal, Switch } from 'antd';
|
||||
import { Agent, ModelAPI, AiModel } from '../../../api';
|
||||
import { parseAgentModels } from '../utils/agentModels';
|
||||
import './ChatHeader.css';
|
||||
|
||||
function formatAgentModel(raw: string | null | undefined, models: AiModel[]) {
|
||||
function formatAgentModel(raw: unknown, models: AiModel[]) {
|
||||
const findModelName = (id: string) => {
|
||||
const model = models.find(m => m.id === id);
|
||||
return model ? model.model_name : id;
|
||||
};
|
||||
|
||||
const s = String(raw ?? '').trim();
|
||||
if (!s) return '默认模型';
|
||||
if (s.startsWith('[') || s.startsWith('{')) {
|
||||
try {
|
||||
const parsed = JSON.parse(s);
|
||||
if (Array.isArray(parsed)) {
|
||||
const names = parsed
|
||||
.map((x: any) => {
|
||||
if (typeof x === 'string') return findModelName(x);
|
||||
return String(x?.name || x?.model || findModelName(x?.id) || '').trim();
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (names.length > 2) {
|
||||
return names.slice(0, 2).join(', ') + '...';
|
||||
}
|
||||
if (names.length) return names.join(', ');
|
||||
} else if (parsed && typeof parsed === 'object') {
|
||||
const name = String((parsed as any).name || (parsed as any).model || findModelName((parsed as any).id) || '').trim();
|
||||
if (name) return name;
|
||||
}
|
||||
} catch {}
|
||||
const parsedModels = parseAgentModels(raw)
|
||||
.map((item) => findModelName(item.id) || item.name)
|
||||
.filter(Boolean);
|
||||
if (parsedModels.length > 2) {
|
||||
return parsedModels.slice(0, 2).join(', ') + '...';
|
||||
}
|
||||
return findModelName(s);
|
||||
if (parsedModels.length > 0) {
|
||||
return parsedModels.join(', ');
|
||||
}
|
||||
const s = String(raw ?? '').trim();
|
||||
return s ? findModelName(s) : '默认模型';
|
||||
}
|
||||
|
||||
const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/');
|
||||
|
|
@ -55,7 +44,7 @@ export default function ChatHeader(props: {
|
|||
ModelAPI.list().then(setModels).catch(console.error);
|
||||
}, []);
|
||||
|
||||
const modelText = formatAgentModel(agent.model, models);
|
||||
const modelText = formatAgentModel(agent.models ?? agent.model, models);
|
||||
|
||||
return (
|
||||
<div className="chat-header">
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function useChatData(args: {
|
|||
}
|
||||
const a = await AgentAPI.detail(agentId);
|
||||
setAgent(a);
|
||||
const models = parseAgentModels(a.model);
|
||||
const models = parseAgentModels(a.models ?? a.model);
|
||||
const firstModel = models[0];
|
||||
if (firstModel) {
|
||||
setOverrides((o) => ({
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export function useChatSender(args: {
|
|||
});
|
||||
const [sessionRefresh, setSessionRefresh] = useState(0);
|
||||
|
||||
const agentModels = useMemo(() => parseAgentModels(agent?.model), [agent?.model]);
|
||||
const agentModels = useMemo(() => parseAgentModels(agent?.models ?? agent?.model), [agent?.model, agent?.models]);
|
||||
const modelOptions = useMemo(() => agentModels.map((model) => ({ value: model.id, label: model.name })), [agentModels]);
|
||||
const activeModelValue = overrides.model_id || '';
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ export function useChatSender(args: {
|
|||
let targetModel: string;
|
||||
let targetModelId: string;
|
||||
if (targetAgent && targetAgent.id !== agentId) {
|
||||
const models = parseAgentModels(targetAgent.model);
|
||||
const models = parseAgentModels(targetAgent.models ?? targetAgent.model);
|
||||
targetModel = models[0]?.name || '';
|
||||
targetModelId = models[0]?.id || '';
|
||||
} else {
|
||||
|
|
@ -166,7 +166,10 @@ export function useChatSender(args: {
|
|||
onRetry: (data) => {
|
||||
setStreaming((s) => ({ ...s, retryInfo: data }));
|
||||
if (data?.stage === 'fallback_model' && data?.toModel) {
|
||||
setOverrides((o) => ({ ...o, model: String(data.toModel) }));
|
||||
const nextModel = parseAgentModels(data.toModel)[0];
|
||||
if (nextModel) {
|
||||
setOverrides((o) => ({ ...o, model: nextModel.name, model_id: nextModel.id }));
|
||||
}
|
||||
}
|
||||
},
|
||||
onReasoningDelta: (chunk) =>
|
||||
|
|
@ -272,7 +275,7 @@ export function useChatSender(args: {
|
|||
let targetModel: string;
|
||||
let targetModelId: string;
|
||||
if (targetAgent && targetAgent.id !== agentId) {
|
||||
const models = parseAgentModels(targetAgent.model);
|
||||
const models = parseAgentModels(targetAgent.models ?? targetAgent.model);
|
||||
targetModel = models[0]?.name || '';
|
||||
targetModelId = models[0]?.id || '';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,46 @@ export interface AgentModelOption {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export const parseAgentModels = (value?: string): AgentModelOption[] => {
|
||||
// parseAgentModelItem 将后端多种模型结构统一转换为聊天侧使用的模型选项。
|
||||
const parseAgentModelItem = (item: any): AgentModelOption | null => {
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
if (typeof item === 'string') {
|
||||
return { id: item, name: item };
|
||||
}
|
||||
if (typeof item === 'object' && item.model?.id) {
|
||||
return {
|
||||
id: String(item.model.id),
|
||||
name: String(item.model.name || item.model.id)
|
||||
};
|
||||
}
|
||||
if (typeof item === 'object' && item.id) {
|
||||
return {
|
||||
id: String(item.id),
|
||||
name: String(item.name || item.model || item.id)
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: String(item),
|
||||
name: String(item)
|
||||
};
|
||||
};
|
||||
|
||||
// parseAgentModels 负责兼容旧 model 字符串和新 models 数组结构。
|
||||
export const parseAgentModels = (value?: unknown): AgentModelOption[] => {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(parseAgentModelItem).filter(Boolean) as AgentModelOption[];
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
const parsed = JSON.parse(String(value));
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map((item: any) => ({
|
||||
id: typeof item === 'object' ? item.id : String(item),
|
||||
name: typeof item === 'object' ? item.name : String(item)
|
||||
}));
|
||||
return parsed.map(parseAgentModelItem).filter(Boolean) as AgentModelOption[];
|
||||
}
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
const single = parseAgentModelItem(parsed);
|
||||
return single ? [single] : [];
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
|
|
@ -22,4 +53,3 @@ export const parseAgentModels = (value?: string): AgentModelOption[] => {
|
|||
.filter(Boolean)
|
||||
.map((item) => ({ id: item, name: item }));
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue