refactor(agent-editor): use dropdown checkbox model picker
parent
1d79e929f9
commit
6ad8a1517f
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Button, Form, Input, InputNumber, Modal, Upload, App as AntApp, List, Popconfirm, Tag, Switch, Select, Collapse, Checkbox } from 'antd';
|
import { Button, Form, Input, InputNumber, Modal, Upload, App as AntApp, List, Popconfirm, Tag, Switch, Select, Collapse, Checkbox, Dropdown } from 'antd';
|
||||||
import type { UploadFile } from 'antd/es/upload/interface';
|
import type { UploadFile } from 'antd/es/upload/interface';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Agent, AgentAPI, ImageAPI, KnowledgeStatus, SkillType, Team, TeamAPI, AiModel, ModelAPI } from '../api';
|
import { Agent, AgentAPI, ImageAPI, KnowledgeStatus, SkillType, Team, TeamAPI, AiModel, ModelAPI } from '../api';
|
||||||
|
|
@ -7,7 +7,7 @@ import { DEFAULT_RH_40X40_GRAY } from '../constants';
|
||||||
import SkillEditor from '../components/SkillEditor';
|
import SkillEditor from '../components/SkillEditor';
|
||||||
import McpPanel from '../components/McpPanel';
|
import McpPanel from '../components/McpPanel';
|
||||||
import ChatPreview from '../components/ChatPreview';
|
import ChatPreview from '../components/ChatPreview';
|
||||||
import { ArrowLeftOutlined, SaveOutlined, FileTextOutlined, RocketOutlined, ToolOutlined, DatabaseOutlined, SettingOutlined, UploadOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, SaveOutlined, FileTextOutlined, RocketOutlined, ToolOutlined, DatabaseOutlined, SettingOutlined, UploadOutlined, DownOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const STATUS_TAG: Record<KnowledgeStatus, { color: string; text: string }> = {
|
const STATUS_TAG: Record<KnowledgeStatus, { color: string; text: string }> = {
|
||||||
pending: { color: 'default', text: '待处理' },
|
pending: { color: 'default', text: '待处理' },
|
||||||
|
|
@ -35,6 +35,67 @@ const parseModelSelections = (value?: string | string[]) =>
|
||||||
.map((item) => item.trim())
|
.map((item) => item.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
function ModelCheckboxDropdown({
|
||||||
|
value = [],
|
||||||
|
onChange,
|
||||||
|
models,
|
||||||
|
}: {
|
||||||
|
value?: string[];
|
||||||
|
onChange?: (value: string[]) => void;
|
||||||
|
models: AiModel[];
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const summary = value.length ? `${value.length} 个已选` : '选择模型';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
popupRender={() => (
|
||||||
|
<div className="agent-model-dropdown-panel" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={value}
|
||||||
|
onChange={(checked) => onChange?.(checked.map((item) => String(item)))}
|
||||||
|
className="agent-model-checkbox-group"
|
||||||
|
>
|
||||||
|
{models.map((m) => {
|
||||||
|
const inputPrice = 2 * m.model_ratio;
|
||||||
|
const outputPrice = inputPrice * m.completion_ratio;
|
||||||
|
return (
|
||||||
|
<Checkbox key={m.model_name} value={m.model_name} className="agent-model-checkbox-item">
|
||||||
|
<div className="agent-model-checkbox-content">
|
||||||
|
<div className="agent-model-checkbox-meta">
|
||||||
|
<img
|
||||||
|
src={m.icon || DEFAULT_RH_40X40_GRAY}
|
||||||
|
alt={m.model_name}
|
||||||
|
className="agent-model-checkbox-icon"
|
||||||
|
/>
|
||||||
|
<span className="agent-model-checkbox-name">{m.model_name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="agent-model-checkbox-price">
|
||||||
|
<span>输入: ${inputPrice.toFixed(2)}/M</span>
|
||||||
|
<span>输出: ${outputPrice.toFixed(2)}/M</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Checkbox>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Checkbox.Group>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button type="button" className="agent-model-dropdown-trigger">
|
||||||
|
<span className="agent-model-dropdown-summary">{summary}</span>
|
||||||
|
<span className="agent-model-dropdown-values">
|
||||||
|
{value.length ? value.join(', ') : '未选择'}
|
||||||
|
</span>
|
||||||
|
<DownOutlined className="agent-model-dropdown-arrow" />
|
||||||
|
</button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function AgentEditor() {
|
export default function AgentEditor() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const isNew = !id;
|
const isNew = !id;
|
||||||
|
|
@ -493,30 +554,7 @@ export default function AgentEditor() {
|
||||||
Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean).join(', ') : ''
|
Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean).join(', ') : ''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Checkbox.Group className="agent-model-checkbox-group">
|
<ModelCheckboxDropdown models={models} />
|
||||||
{models.map((m) => {
|
|
||||||
const inputPrice = 2 * m.model_ratio;
|
|
||||||
const outputPrice = inputPrice * m.completion_ratio;
|
|
||||||
return (
|
|
||||||
<Checkbox key={m.model_name} value={m.model_name} className="agent-model-checkbox-item">
|
|
||||||
<div className="agent-model-checkbox-content">
|
|
||||||
<div className="agent-model-checkbox-meta">
|
|
||||||
<img
|
|
||||||
src={m.icon || DEFAULT_RH_40X40_GRAY}
|
|
||||||
alt={m.model_name}
|
|
||||||
className="agent-model-checkbox-icon"
|
|
||||||
/>
|
|
||||||
<span className="agent-model-checkbox-name">{m.model_name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="agent-model-checkbox-price">
|
|
||||||
<span>输入: ${inputPrice.toFixed(2)}/M</span>
|
|
||||||
<span>输出: ${outputPrice.toFixed(2)}/M</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Checkbox>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Checkbox.Group>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="temperature"
|
name="temperature"
|
||||||
|
|
|
||||||
|
|
@ -561,6 +561,53 @@ body {
|
||||||
box-shadow: var(--shadow-focus);
|
box-shadow: var(--shadow-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-model-dropdown-trigger {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 42px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-model-dropdown-summary {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-model-dropdown-values {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-model-dropdown-arrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-model-dropdown-panel {
|
||||||
|
width: min(720px, calc(100vw - 64px));
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-model-checkbox-group {
|
.agent-model-checkbox-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue