fix: 对齐智能体模型新接口并放宽登录手机号校验
parent
46ddf0652a
commit
409bbcc297
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
// 兼容旧格式:逗号分隔的字符串
|
// 兼容旧格式:逗号分隔的字符串
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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) => ({
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue