fix: 对齐智能体模型新接口并放宽登录手机号校验

main
sp mac bookpro 2605 2026-08-04 00:51:44 +08:00
parent 46ddf0652a
commit 409bbcc297
9 changed files with 133 additions and 60 deletions

View File

@ -73,13 +73,24 @@ export interface ExternalToolPlugin extends ExternalToolPluginPayload {
isTemp?: boolean; isTemp?: boolean;
} }
export interface AgentModelConfig {
model: {
id: string;
name: string;
};
role: string;
priority: number;
enabled: boolean;
}
export interface Agent { export interface Agent {
id: string; id: string;
name: string; name: string;
description: string; description: string;
avatar: string; avatar: string;
prompt: string; prompt: string;
model: string; model?: string;
models?: AgentModelConfig[];
temperature: number; temperature: number;
owner_id?: string | null; owner_id?: string | null;
team_id?: string | null; team_id?: string | null;

View File

@ -9,6 +9,7 @@ import {
ToolCallTrace ToolCallTrace
} from '../api'; } from '../api';
import Markdown from './Markdown'; import Markdown from './Markdown';
import { parseAgentModels } from '../pages/chat/utils/agentModels';
interface Props { interface Props {
agent: Agent; agent: Agent;
@ -74,8 +75,9 @@ export default function ChatPreview({ agent, agentId }: Props) {
sid = created.id; sid = created.id;
setSessionId(sid); setSessionId(sid);
} }
const model = String(agent?.model || '').split(',')[0]?.trim() || undefined; const agentModels = parseAgentModels(agent?.models ?? agent?.model);
const modelId = model ? undefined : undefined; const model = agentModels[0]?.name || undefined;
const modelId = agentModels[0]?.id || undefined;
const targetAgentId = agentId; const targetAgentId = agentId;
await streamChat( await streamChat(
sid, sid,

View File

@ -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 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)) { if (Array.isArray(value)) {
return value; return value.map(parseModelSelectionItem).filter(Boolean);
} }
// 尝试解析 JSON 格式 // 尝试解析 JSON 格式
try { try {
const parsed = JSON.parse(String(value || '[]')); const parsed = JSON.parse(String(value || '[]'));
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
return parsed.map((item: any) => { return parsed.map(parseModelSelectionItem).filter(Boolean);
if (typeof item === 'object' && item.id) {
return item.id;
}
return String(item);
}).filter(Boolean);
} }
} catch { } catch {
// 兼容旧格式:逗号分隔的字符串 // 兼容旧格式:逗号分隔的字符串

View File

@ -1,7 +1,7 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { FormInstance } from 'antd'; import { FormInstance } from 'antd';
import { Agent, AgentAPI, Team, TeamAPI, AiModel, ModelAPI, ImageAPI } from '../../../api'; import { Agent, AgentAPI, AgentModelConfig, Team, TeamAPI, AiModel, ModelAPI, ImageAPI } from '../../../api';
import { DEFAULT_AVATAR } from '../constants'; import { DEFAULT_AVATAR, parseModelSelections } from '../constants';
interface UseAgentEditorOptions { interface UseAgentEditorOptions {
id?: string; id?: string;
@ -11,6 +11,31 @@ interface UseAgentEditorOptions {
navigate: any; 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) { export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentEditorOptions) {
const [agent, setAgent] = useState<Agent | null>(null); const [agent, setAgent] = useState<Agent | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@ -85,7 +110,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
setAgent(data); setAgent(data);
if (force || autoSaveStatus !== 'dirty') { if (force || autoSaveStatus !== 'dirty') {
hydratingRef.current = true; hydratingRef.current = true;
form.setFieldsValue(data); form.setFieldsValue(normalizeAgentFormValues(data));
window.setTimeout(() => { window.setTimeout(() => {
hydratingRef.current = false; hydratingRef.current = false;
}, 0); }, 0);
@ -163,14 +188,15 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
const formValue = (values as any)[key]; const formValue = (values as any)[key];
const originalValue = (agent as any)?.[key]; const originalValue = (agent as any)?.[key];
// 特殊处理 model 字段:将 id 和 name 组合成 JSON 数组字符串 // model 字段在表单内是单值,提交给后端时需要转换为新的 models 数组结构。
if (key === 'model' && Array.isArray(formValue)) { if (key === 'model') {
const modelObjects = formValue.map((modelId: string) => { const nextModels = buildAgentModelsPayload(formValue, models);
const model = models.find((m) => m.id === modelId); if (JSON.stringify(nextModels) !== JSON.stringify(agent?.models || [])) {
return { id: modelId, name: model?.model_name || '' }; changedFields.models = nextModels;
}); }
changedFields[key] = JSON.stringify(modelObjects); return;
} else if (formValue !== originalValue) { }
if (formValue !== originalValue) {
changedFields[key] = formValue; changedFields[key] = formValue;
} }
}); });
@ -181,7 +207,7 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
} }
const updatedAgent = await AgentAPI.update(id!, changedFields); const updatedAgent = await AgentAPI.update(id!, changedFields);
setAgent(updatedAgent); setAgent(updatedAgent);
form.setFieldsValue(updatedAgent); form.setFieldsValue(normalizeAgentFormValues(updatedAgent));
if (!silent) message.success('已保存'); if (!silent) message.success('已保存');
setAutoSaveStatus('saved'); setAutoSaveStatus('saved');
} catch (e) { } catch (e) {

View File

@ -42,7 +42,6 @@ export default function LoginFormCard({ logic }: { logic: LoginPageLogic }) {
label="手机号" label="手机号"
rules={[ rules={[
{ required: true, message: '请填写手机号' }, { required: true, message: '请填写手机号' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式' }
]} ]}
> >
<Input placeholder="13800138000" size="large" autoFocus /> <Input placeholder="13800138000" size="large" autoFocus />

View File

@ -2,37 +2,26 @@ import { useState, useEffect } from 'react';
import { ApiOutlined, DeleteOutlined, EditOutlined, SettingOutlined, EllipsisOutlined, HistoryOutlined, LogoutOutlined } from '@ant-design/icons'; import { ApiOutlined, DeleteOutlined, EditOutlined, SettingOutlined, EllipsisOutlined, HistoryOutlined, LogoutOutlined } from '@ant-design/icons';
import { Button, Dropdown, Modal, Switch } from 'antd'; import { Button, Dropdown, Modal, Switch } from 'antd';
import { Agent, ModelAPI, AiModel } from '../../../api'; import { Agent, ModelAPI, AiModel } from '../../../api';
import { parseAgentModels } from '../utils/agentModels';
import './ChatHeader.css'; import './ChatHeader.css';
function formatAgentModel(raw: string | null | undefined, models: AiModel[]) { function formatAgentModel(raw: unknown, models: AiModel[]) {
const findModelName = (id: string) => { const findModelName = (id: string) => {
const model = models.find(m => m.id === id); const model = models.find(m => m.id === id);
return model ? model.model_name : id; return model ? model.model_name : id;
}; };
const s = String(raw ?? '').trim(); const parsedModels = parseAgentModels(raw)
if (!s) return '默认模型'; .map((item) => findModelName(item.id) || item.name)
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); .filter(Boolean);
if (names.length > 2) { if (parsedModels.length > 2) {
return names.slice(0, 2).join(', ') + '...'; return parsedModels.slice(0, 2).join(', ') + '...';
} }
if (names.length) return names.join(', '); if (parsedModels.length > 0) {
} else if (parsed && typeof parsed === 'object') { return parsedModels.join(', ');
const name = String((parsed as any).name || (parsed as any).model || findModelName((parsed as any).id) || '').trim();
if (name) return name;
} }
} catch {} const s = String(raw ?? '').trim();
} return s ? findModelName(s) : '默认模型';
return findModelName(s);
} }
const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/'); 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); ModelAPI.list().then(setModels).catch(console.error);
}, []); }, []);
const modelText = formatAgentModel(agent.model, models); const modelText = formatAgentModel(agent.models ?? agent.model, models);
return ( return (
<div className="chat-header"> <div className="chat-header">

View File

@ -36,7 +36,7 @@ export function useChatData(args: {
} }
const a = await AgentAPI.detail(agentId); const a = await AgentAPI.detail(agentId);
setAgent(a); setAgent(a);
const models = parseAgentModels(a.model); const models = parseAgentModels(a.models ?? a.model);
const firstModel = models[0]; const firstModel = models[0];
if (firstModel) { if (firstModel) {
setOverrides((o) => ({ setOverrides((o) => ({

View File

@ -114,7 +114,7 @@ export function useChatSender(args: {
}); });
const [sessionRefresh, setSessionRefresh] = useState(0); 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 modelOptions = useMemo(() => agentModels.map((model) => ({ value: model.id, label: model.name })), [agentModels]);
const activeModelValue = overrides.model_id || ''; const activeModelValue = overrides.model_id || '';
@ -141,7 +141,7 @@ export function useChatSender(args: {
let targetModel: string; let targetModel: string;
let targetModelId: string; let targetModelId: string;
if (targetAgent && targetAgent.id !== agentId) { if (targetAgent && targetAgent.id !== agentId) {
const models = parseAgentModels(targetAgent.model); const models = parseAgentModels(targetAgent.models ?? targetAgent.model);
targetModel = models[0]?.name || ''; targetModel = models[0]?.name || '';
targetModelId = models[0]?.id || ''; targetModelId = models[0]?.id || '';
} else { } else {
@ -166,7 +166,10 @@ export function useChatSender(args: {
onRetry: (data) => { onRetry: (data) => {
setStreaming((s) => ({ ...s, retryInfo: data })); setStreaming((s) => ({ ...s, retryInfo: data }));
if (data?.stage === 'fallback_model' && data?.toModel) { 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) => onReasoningDelta: (chunk) =>
@ -272,7 +275,7 @@ export function useChatSender(args: {
let targetModel: string; let targetModel: string;
let targetModelId: string; let targetModelId: string;
if (targetAgent && targetAgent.id !== agentId) { if (targetAgent && targetAgent.id !== agentId) {
const models = parseAgentModels(targetAgent.model); const models = parseAgentModels(targetAgent.models ?? targetAgent.model);
targetModel = models[0]?.name || ''; targetModel = models[0]?.name || '';
targetModelId = models[0]?.id || ''; targetModelId = models[0]?.id || '';
} else { } else {

View File

@ -3,15 +3,46 @@ export interface AgentModelOption {
name: string; 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 (!value) return [];
if (Array.isArray(value)) {
return value.map(parseAgentModelItem).filter(Boolean) as AgentModelOption[];
}
try { try {
const parsed = JSON.parse(value); const parsed = JSON.parse(String(value));
if (Array.isArray(parsed)) { if (Array.isArray(parsed)) {
return parsed.map((item: any) => ({ return parsed.map(parseAgentModelItem).filter(Boolean) as AgentModelOption[];
id: typeof item === 'object' ? item.id : String(item), }
name: typeof item === 'object' ? item.name : String(item) if (parsed && typeof parsed === 'object') {
})); const single = parseAgentModelItem(parsed);
return single ? [single] : [];
} }
} catch { } catch {
// ignore // ignore
@ -22,4 +53,3 @@ export const parseAgentModels = (value?: string): AgentModelOption[] => {
.filter(Boolean) .filter(Boolean)
.map((item) => ({ id: item, name: item })); .map((item) => ({ id: item, name: item }));
}; };