(feature)(webapp) agent defaults to list mode, supports switching to card mode (#362)

This commit is contained in:
williamhliu
2023-11-10 18:21:59 +08:00
committed by GitHub
parent f998f27c6f
commit bd541e1199
6 changed files with 263 additions and 93 deletions

View File

@@ -328,9 +328,9 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
className={styles.composerInput}
placeholder={
currentAgent
? `智能助理${
isMobile ? `[${currentAgent?.name}]` : `${currentAgent?.name}`
}将与您对话,输入“/”可切换助理`
? `${currentAgent.name}】将与您对话,点击${!isMobile ? '左侧' : ''}智能助理${
!isMobile ? '列表' : ''
}可切换`
: '请输入您的问题'
}
value={inputMsg}

View File

@@ -9,18 +9,19 @@ import { CheckCircleFilled, UpOutlined } from '@ant-design/icons';
import { SqlInfoType } from '../../common/type';
type Props = {
llmReq?: any;
integrateSystem?: string;
sqlInfo: SqlInfoType;
sqlTimeCost?: number;
};
const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo, sqlTimeCost }) => {
const SqlItem: React.FC<Props> = ({ llmReq, integrateSystem, sqlInfo, sqlTimeCost }) => {
const [sqlType, setSqlType] = useState('');
const tipPrefixCls = `${PREFIX_CLS}-item`;
const prefixCls = `${PREFIX_CLS}-sql-item`;
const handleCopy = (text, result) => {
const handleCopy = (_: string, result: any) => {
result ? message.success('复制SQL成功', 1) : message.error('复制SQL失败', 1);
};
@@ -28,10 +29,12 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo, sqlTimeCost }) =>
setSqlType('');
};
if (!sqlInfo.s2SQL && !sqlInfo.correctS2SQL && !sqlInfo.querySQL) {
if (!llmReq && !sqlInfo.s2SQL && !sqlInfo.correctS2SQL && !sqlInfo.querySQL) {
return null;
}
const { schema, linking, priorExts } = llmReq || {};
return (
<div className={`${tipPrefixCls}-parse-tip`}>
<div className={`${tipPrefixCls}-title-bar`}>
@@ -49,6 +52,18 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo, sqlTimeCost }) =>
)}
</div>
<div className={`${tipPrefixCls}-content-options`}>
{llmReq && (
<div
className={`${tipPrefixCls}-content-option ${
sqlType === 'schemaMap' ? `${tipPrefixCls}-content-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'schemaMap' ? '' : 'schemaMap');
}}
>
Schema映射
</div>
)}
{sqlInfo.s2SQL && (
<div
className={`${tipPrefixCls}-content-option ${
@@ -96,6 +111,38 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo, sqlTimeCost }) =>
: ''
}`}
>
{sqlType === 'schemaMap' && (
<div className={`${prefixCls}-code`}>
{schema?.fieldNameList?.length > 0 && (
<div className={`${prefixCls}-schema-row`}>
<div className={`${prefixCls}-schema-title`}></div>
<div className={`${prefixCls}-schema-content`}>
{schema.fieldNameList.join('、')}
</div>
</div>
)}
{linking?.length > 0 && (
<div className={`${prefixCls}-schema-row`}>
<div className={`${prefixCls}-schema-title`}></div>
<div className={`${prefixCls}-schema-content`}>
{linking.map((item: any) => {
return (
<span>
{item.fieldName}: {item.fieldValue}
</span>
);
})}
</div>
</div>
)}
{priorExts && (
<div className={`${prefixCls}-schema-row`}>
<div className={`${prefixCls}-schema-title`}></div>
<div className={`${prefixCls}-schema-content`}>{priorExts}</div>
</div>
)}
</div>
)}
{sqlType && sqlInfo[sqlType] && (
<>
<SyntaxHighlighter

View File

@@ -307,6 +307,7 @@ const ChatItem: React.FC<Props> = ({
<>
{!isMobile && parseInfo?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && (
<SqlItem
llmReq={parseInfo?.properties?.CONTEXT?.llmReq}
integrateSystem={integrateSystem}
sqlInfo={parseInfo.sqlInfo}
sqlTimeCost={parseTimeCost?.sqlTime}

View File

@@ -447,6 +447,26 @@
color: var(--chat-blue);
cursor: pointer;
}
&-schema-row {
display: flex;
margin-top: 10px;
&:first-child {
margin-top: 0;
}
}
&-schema-title {
width: 50px;
color: var(--text-color);
font-weight: 500;
}
&-schema-content {
flex: 1;
color: var(--text-color);
}
}
.@{sql-item-prefix-cls}-copilot {

View File

@@ -1,12 +1,11 @@
import { DeleteOutlined, EditOutlined, PlusOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Input, Popconfirm, Switch } from 'antd';
import { Button, Popconfirm, Switch, Table } from 'antd';
import classNames from 'classnames';
import moment from 'moment';
import { useEffect, useState } from 'react';
import styles from './style.less';
import { AgentType } from './type';
const { Search } = Input;
type Props = {
agents: AgentType[];
currentAgent?: AgentType;
@@ -25,33 +24,108 @@ const AgentsSection: React.FC<Props> = ({
onEditAgent,
onSaveAgent,
}) => {
// const [searchName, setSearchName] = useState('');
const [showAgents, setShowAgents] = useState<AgentType[]>([]);
const [showType, setShowType] = useState(localStorage.getItem('AGENT_SHOW_TYPE') || 'list');
useEffect(() => {
setShowAgents(agents);
}, [agents]);
const columns = [
{
title: '助理名称',
dataIndex: 'name',
key: 'name',
render: (value: string, agent: AgentType) => {
return (
<a
onClick={() => {
onSelectAgent(agent);
}}
>
{value}
</a>
);
},
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: number, agent: AgentType) => {
return (
<div className={styles.toggleStatus}>
{status === 0 ? '已禁用' : <span className={styles.online}></span>}
<span
onClick={(e) => {
e.stopPropagation();
}}
>
<Switch
size="small"
defaultChecked={status === 1}
onChange={(value) => {
onSaveAgent({ ...agent, status: value ? 1 : 0 }, true);
}}
/>
</span>
</div>
);
},
},
{
title: '更新人',
dataIndex: 'updatedBy',
key: 'updatedBy',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
key: 'updatedAt',
render: (value: string) => {
return moment(value).format('YYYY-MM-DD HH:mm:ss');
},
},
{
title: '操作',
dataIndex: 'x',
key: 'x',
render: (_: any, agent: AgentType) => {
return (
<div className={styles.operateIcons}>
<a
onClick={() => {
onSelectAgent(agent);
}}
>
</a>
<Popconfirm
title="确定删除吗?"
onCancel={(e) => {
e?.stopPropagation();
}}
onConfirm={() => {
onDeleteAgent(agent.id!);
}}
>
<a></a>
</Popconfirm>
</div>
);
},
},
];
return (
<div className={styles.agentsSection}>
{/* <div className={styles.sectionTitle}>问答助理</div> */}
<div className={styles.content}>
<div className={styles.searchBar}>
{/* <Search
placeholder="请输入助理名称搜索"
className={styles.searchControl}
value={searchName}
onChange={(e) => {
setSearchName(e.target.value);
}}
onSearch={(value) => {
setShowAgents(
agents.filter((agent) =>
agent.name?.trim().toLowerCase().includes(value.trim().toLowerCase()),
),
);
}}
/> */}
<Button
type="primary"
onClick={() => {
@@ -61,81 +135,97 @@ const AgentsSection: React.FC<Props> = ({
<PlusOutlined />
</Button>
<div className={styles.switchShowType}>
<span className={styles.switchShowTypeLabel}></span>
<Switch
size="small"
checked={showType === 'card'}
onChange={(value) => {
const showTypeValue = value ? 'card' : 'list';
setShowType(showTypeValue);
localStorage.setItem('AGENT_SHOW_TYPE', showTypeValue);
}}
/>
</div>
</div>
<div className={styles.agentsContainer}>
{showAgents.map((agent) => {
const agentItemClass = classNames(styles.agentItem, {
[styles.agentActive]: agent.id === currentAgent?.id,
});
return (
<div
className={agentItemClass}
key={agent.id}
onClick={() => {
onSelectAgent(agent);
}}
>
<UserOutlined className={styles.agentIcon} />
<div className={styles.agentContent}>
<div className={styles.agentNameBar}>
<div className={styles.agentName}>{agent.name}</div>
<div className={styles.operateIcons}>
<EditOutlined
className={styles.operateIcon}
onClick={(e) => {
e.stopPropagation();
onEditAgent(agent);
}}
/>
<Popconfirm
title="确定删除吗?"
onCancel={(e) => {
e?.stopPropagation();
}}
onConfirm={(e) => {
e?.stopPropagation();
onDeleteAgent(agent.id!);
}}
>
<DeleteOutlined
{showType === 'list' ? (
<Table columns={columns} dataSource={showAgents} />
) : (
<div className={styles.agentsContainer}>
{showAgents.map((agent) => {
const agentItemClass = classNames(styles.agentItem, {
[styles.agentActive]: agent.id === currentAgent?.id,
});
return (
<div
className={agentItemClass}
key={agent.id}
onClick={() => {
onSelectAgent(agent);
}}
>
<UserOutlined className={styles.agentIcon} />
<div className={styles.agentContent}>
<div className={styles.agentNameBar}>
<div className={styles.agentName}>{agent.name}</div>
<div className={styles.operateIcons}>
<EditOutlined
className={styles.operateIcon}
onClick={(e) => {
e.stopPropagation();
onEditAgent(agent);
}}
/>
</Popconfirm>
</div>
</div>
<div className={styles.bottomBar}>
<div className={styles.agentDescription} title={agent.description}>
{agent.description}
</div>
<div className={styles.toggleStatus}>
{agent.status === 0 ? (
'已禁用'
) : (
<span className={styles.online}></span>
)}
<span
onClick={(e) => {
e.stopPropagation();
}}
>
<Switch
size="small"
defaultChecked={agent.status === 1}
onChange={(value) => {
onSaveAgent({ ...agent, status: value ? 1 : 0 }, true);
<Popconfirm
title="确定删除吗?"
onCancel={(e) => {
e?.stopPropagation();
}}
/>
</span>
onConfirm={(e) => {
e?.stopPropagation();
onDeleteAgent(agent.id!);
}}
>
<DeleteOutlined
className={styles.operateIcon}
onClick={(e) => {
e.stopPropagation();
}}
/>
</Popconfirm>
</div>
</div>
<div className={styles.bottomBar}>
<div className={styles.agentDescription} title={agent.description}>
{agent.description}
</div>
<div className={styles.toggleStatus}>
{agent.status === 0 ? (
'已禁用'
) : (
<span className={styles.online}></span>
)}
<span
onClick={(e) => {
e.stopPropagation();
}}
>
<Switch
size="small"
defaultChecked={agent.status === 1}
onChange={(value) => {
onSaveAgent({ ...agent, status: value ? 1 : 0 }, true);
}}
/>
</span>
</div>
</div>
</div>
</div>
</div>
);
})}
</div>
);
})}
</div>
)}
</div>
</div>
);

View File

@@ -1,5 +1,4 @@
.agent {
// background: #fff;
height: calc(100vh - 48px);
}
@@ -20,7 +19,7 @@
.searchBar {
display: flex;
align-items: center;
column-gap: 20px;
column-gap: 30px;
margin-bottom: 40px;
.searchControl {
@@ -290,3 +289,16 @@
}
}
}
.switchShowTypeLabel {
color: #999;
font-weight: 500;
font-size: 14px;
margin-right: 12px;
}
.operateIcons {
display: flex;
align-items: center;
column-gap: 12px;
}