@yuku123/z-agent-frontend-component 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/z-agent-frontend-component.css +1 -0
- package/dist/z-agent-frontend-component.es.js +9956 -0
- package/dist/z-agent-frontend-component.umd.js +219 -0
- package/package.json +77 -0
- package/src/api/apiRouter.js +78 -0
- package/src/api/index.js +23 -0
- package/src/api/request.js +59 -0
- package/src/api/routes.js +140 -0
- package/src/dev.jsx +80 -0
- package/src/index.js +86 -0
- package/src/pages/agent/app/index.jsx +2 -0
- package/src/pages/agent/editor/AgentAppEditor.jsx +456 -0
- package/src/pages/agent/editor/WorkflowEditor.jsx +495 -0
- package/src/pages/agent/editor/nodes/index.ts +225 -0
- package/src/pages/agent/index.jsx +1379 -0
- package/src/pages/agent/share.jsx +512 -0
- package/src/pages/ak/AkUsageDrawer.jsx +208 -0
- package/src/pages/ak/index.jsx +496 -0
- package/src/pages/llm/index.jsx +736 -0
- package/src/pages/llm/model/index.jsx +220 -0
- package/src/pages/llm/provider/index.jsx +173 -0
- package/src/pages/mcp/index.jsx +359 -0
- package/src/pages/oss/BucketList.jsx +320 -0
- package/src/pages/oss/ObjectBrowser.jsx +409 -0
- package/src/pages/product/execute.jsx +608 -0
- package/src/pages/product/index.jsx +628 -0
- package/src/pages/product/scene.jsx +746 -0
- package/src/pages/script/ApiBridgeEditor.jsx +255 -0
- package/src/pages/script/CurlImportModal.jsx +263 -0
- package/src/pages/script/FieldMappingEditor.jsx +131 -0
- package/src/pages/script/OpenApiImportModal.jsx +212 -0
- package/src/pages/script/index.jsx +532 -0
- package/src/pages/skill/index.jsx +1595 -0
- package/src/pages/trace/DebugPlayground.jsx +357 -0
- package/src/pages/trace/components/MetricsDashboard.jsx +164 -0
- package/src/pages/trace/components/RagFragments.jsx +134 -0
- package/src/pages/trace/components/Timeline.jsx +142 -0
- package/src/pages/trace/components/ToolCallTree.jsx +116 -0
- package/src/pages/trace/index.jsx +13 -0
- package/src/pages/usage/index.jsx +352 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Badge,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
Col,
|
|
7
|
+
Divider,
|
|
8
|
+
Drawer,
|
|
9
|
+
Empty,
|
|
10
|
+
Form,
|
|
11
|
+
Input,
|
|
12
|
+
message,
|
|
13
|
+
Modal,
|
|
14
|
+
Popconfirm,
|
|
15
|
+
Row,
|
|
16
|
+
Select,
|
|
17
|
+
Space,
|
|
18
|
+
Statistic,
|
|
19
|
+
Table,
|
|
20
|
+
Tabs,
|
|
21
|
+
Tag,
|
|
22
|
+
Tooltip
|
|
23
|
+
} from 'antd'
|
|
24
|
+
import {
|
|
25
|
+
CheckCircleOutlined,
|
|
26
|
+
CloudUploadOutlined,
|
|
27
|
+
CopyOutlined,
|
|
28
|
+
DeleteOutlined,
|
|
29
|
+
EditOutlined,
|
|
30
|
+
ExclamationCircleOutlined,
|
|
31
|
+
EyeOutlined,
|
|
32
|
+
FileTextOutlined,
|
|
33
|
+
PlayCircleOutlined,
|
|
34
|
+
PlusOutlined,
|
|
35
|
+
ShopOutlined,
|
|
36
|
+
StopOutlined,
|
|
37
|
+
TagOutlined
|
|
38
|
+
} from '@ant-design/icons'
|
|
39
|
+
import {productApi} from '../../api'
|
|
40
|
+
|
|
41
|
+
const {TextArea} = Input
|
|
42
|
+
const {Option} = Select
|
|
43
|
+
const {TabPane} = Tabs
|
|
44
|
+
|
|
45
|
+
// ===== 产品列表页 =====
|
|
46
|
+
const ProductListPage = () => {
|
|
47
|
+
const [activeTab, setActiveTab] = useState('list')
|
|
48
|
+
return (
|
|
49
|
+
<Card style={{minHeight: 'calc(100vh - 140px)'}}>
|
|
50
|
+
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
|
51
|
+
<TabPane tab={<span><ShopOutlined/> 产品管理</span>} key="list">
|
|
52
|
+
<ProductTablePage/>
|
|
53
|
+
</TabPane>
|
|
54
|
+
<TabPane tab={<span><FileTextOutlined/> 产品配置</span>} key="config">
|
|
55
|
+
<ProductConfigPage/>
|
|
56
|
+
</TabPane>
|
|
57
|
+
</Tabs>
|
|
58
|
+
</Card>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ===== 产品表格 =====
|
|
63
|
+
const ProductTablePage = () => {
|
|
64
|
+
const [dataSource, setDataSource] = useState([])
|
|
65
|
+
const [loading, setLoading] = useState(false)
|
|
66
|
+
const [searchKey, setSearchKey] = useState('')
|
|
67
|
+
const [statusFilter, setStatusFilter] = useState('')
|
|
68
|
+
const [pageNum, setPageNum] = useState(1)
|
|
69
|
+
const [pageSize] = useState(12)
|
|
70
|
+
const [total, setTotal] = useState(0)
|
|
71
|
+
const [modalVisible, setModalVisible] = useState(false)
|
|
72
|
+
const [form] = Form.useForm()
|
|
73
|
+
const [editingProduct, setEditingProduct] = useState(null)
|
|
74
|
+
const [createLoading, setCreateLoading] = useState(false)
|
|
75
|
+
const [detailVisible, setDetailVisible] = useState(false)
|
|
76
|
+
const [detailProduct, setDetailProduct] = useState(null)
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
fetchData()
|
|
80
|
+
}, [pageNum, searchKey, statusFilter])
|
|
81
|
+
|
|
82
|
+
const fetchData = async () => {
|
|
83
|
+
setLoading(true)
|
|
84
|
+
try {
|
|
85
|
+
const res = await productApi.page({productName: searchKey, status: statusFilter, id: pageNum})
|
|
86
|
+
if (res.data) {
|
|
87
|
+
setDataSource(res.data.records || [])
|
|
88
|
+
setTotal(res.data.total || 0)
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
message.error('加载失败: ' + (e.message || ''))
|
|
92
|
+
}
|
|
93
|
+
setLoading(false)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const handleCreate = () => {
|
|
97
|
+
setEditingProduct(null)
|
|
98
|
+
form.resetFields()
|
|
99
|
+
form.setFieldsValue({
|
|
100
|
+
status: 'DRAFT',
|
|
101
|
+
})
|
|
102
|
+
setModalVisible(true)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleEdit = (record) => {
|
|
106
|
+
setEditingProduct(record)
|
|
107
|
+
form.setFieldsValue({
|
|
108
|
+
productCode: record.productCode,
|
|
109
|
+
productName: record.productName,
|
|
110
|
+
description: record.description,
|
|
111
|
+
category: record.category,
|
|
112
|
+
version: record.version || '1.0.0',
|
|
113
|
+
iconUrl: record.iconUrl,
|
|
114
|
+
status: record.status,
|
|
115
|
+
tags: record.tags,
|
|
116
|
+
})
|
|
117
|
+
setModalVisible(true)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handleSubmit = async () => {
|
|
121
|
+
try {
|
|
122
|
+
const values = await form.validateFields()
|
|
123
|
+
setCreateLoading(true)
|
|
124
|
+
if (editingProduct) {
|
|
125
|
+
await productApi.update({id: editingProduct.id, ...values})
|
|
126
|
+
message.success('更新成功')
|
|
127
|
+
} else {
|
|
128
|
+
await productApi.create(values)
|
|
129
|
+
message.success('创建成功')
|
|
130
|
+
}
|
|
131
|
+
setModalVisible(false)
|
|
132
|
+
fetchData()
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (!e.errorFields) message.error('操作失败')
|
|
135
|
+
} finally {
|
|
136
|
+
setCreateLoading(false)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const handleDelete = async (id) => {
|
|
141
|
+
try {
|
|
142
|
+
await productApi.delete(id)
|
|
143
|
+
message.success('删除成功')
|
|
144
|
+
fetchData()
|
|
145
|
+
} catch (e) {
|
|
146
|
+
message.error('删除失败')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const handlePublish = async (productCode) => {
|
|
151
|
+
try {
|
|
152
|
+
await productApi.publish(productCode)
|
|
153
|
+
message.success('发布成功')
|
|
154
|
+
fetchData()
|
|
155
|
+
} catch (e) {
|
|
156
|
+
message.error('发布失败')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const handleOffline = async (productCode) => {
|
|
161
|
+
try {
|
|
162
|
+
await productApi.offline(productCode)
|
|
163
|
+
message.success('下架成功')
|
|
164
|
+
fetchData()
|
|
165
|
+
} catch (e) {
|
|
166
|
+
message.error('下架失败')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleViewDetail = async (record) => {
|
|
171
|
+
setDetailProduct(record)
|
|
172
|
+
setDetailVisible(true)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const copyProductCode = (code) => {
|
|
176
|
+
navigator.clipboard.writeText(code).then(() => message.success('产品编码已复制')).catch(() => {
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const columns = [
|
|
181
|
+
{
|
|
182
|
+
title: '产品',
|
|
183
|
+
key: 'product',
|
|
184
|
+
width: 280,
|
|
185
|
+
render: (_, record) => (
|
|
186
|
+
<div style={{display: 'flex', alignItems: 'center', gap: 10}}>
|
|
187
|
+
<div style={{
|
|
188
|
+
width: 40, height: 40, borderRadius: 8,
|
|
189
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
190
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
|
191
|
+
}}>
|
|
192
|
+
<ShopOutlined style={{fontSize: 18, color: '#fff'}}/>
|
|
193
|
+
</div>
|
|
194
|
+
<div>
|
|
195
|
+
<div style={{fontWeight: 500}}>{record.productName}</div>
|
|
196
|
+
<div style={{fontSize: 12, color: '#999'}}>
|
|
197
|
+
<span style={{fontFamily: 'monospace'}}>{record.productCode}</span>
|
|
198
|
+
<Tooltip title="复制编码">
|
|
199
|
+
<Button size="small" type="text" icon={<CopyOutlined/>}
|
|
200
|
+
onClick={() => copyProductCode(record.productCode)}
|
|
201
|
+
style={{marginLeft: 4, padding: 0}}/>
|
|
202
|
+
</Tooltip>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
title: '分类',
|
|
210
|
+
dataIndex: 'category',
|
|
211
|
+
width: 120,
|
|
212
|
+
render: (val) => val ? <Tag icon={<TagOutlined/>}>{val}</Tag> : <span style={{color: '#ccc'}}>未分类</span>
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
title: '状态',
|
|
216
|
+
dataIndex: 'status',
|
|
217
|
+
width: 100,
|
|
218
|
+
render: (val) => {
|
|
219
|
+
const map = {PUBLISHED: 'success', DRAFT: 'warning', OFFLINE: 'error', ARCHIVED: 'default'}
|
|
220
|
+
const label = {PUBLISHED: '已发布', DRAFT: '草稿', OFFLINE: '已下架', ARCHIVED: '已归档'}
|
|
221
|
+
return <Badge status={map[val] || 'default'} text={label[val] || val}/>
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
title: '版本',
|
|
226
|
+
dataIndex: 'version',
|
|
227
|
+
width: 90,
|
|
228
|
+
render: (v) => <span style={{fontFamily: 'monospace'}}>v{v || '1.0.0'}</span>
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
title: '描述',
|
|
232
|
+
dataIndex: 'description',
|
|
233
|
+
ellipsis: true,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
title: '操作',
|
|
237
|
+
width: 320,
|
|
238
|
+
render: (_, record) => (
|
|
239
|
+
<Space size="small" wrap>
|
|
240
|
+
<Tooltip title="查看详情"><Button size="small" icon={<EyeOutlined/>}
|
|
241
|
+
onClick={() => handleViewDetail(record)}>详情</Button></Tooltip>
|
|
242
|
+
<Tooltip title="编辑配置"><Button size="small" icon={<EditOutlined/>}
|
|
243
|
+
onClick={() => handleEdit(record)}>编辑</Button></Tooltip>
|
|
244
|
+
{record.status === 'DRAFT' && (
|
|
245
|
+
<Tooltip title="发布产品"><Button size="small" type="primary" icon={<CheckCircleOutlined/>}
|
|
246
|
+
onClick={() => handlePublish(record.productCode)}>发布</Button></Tooltip>
|
|
247
|
+
)}
|
|
248
|
+
{record.status === 'PUBLISHED' && (
|
|
249
|
+
<Tooltip title="下架产品"><Button size="small" danger icon={<StopOutlined/>}
|
|
250
|
+
onClick={() => handleOffline(record.productCode)}>下架</Button></Tooltip>
|
|
251
|
+
)}
|
|
252
|
+
<Tooltip title="执行场景"><Button size="small" type="primary" icon={<PlayCircleOutlined/>}
|
|
253
|
+
onClick={() => window.open(`/ai/product/execute?productCode=${record.productCode}`, '_blank')}>执行</Button></Tooltip>
|
|
254
|
+
<Popconfirm title="确定删除此产品?" onConfirm={() => handleDelete(record.id)} okText="删除"
|
|
255
|
+
okButtonProps={{danger: true}}>
|
|
256
|
+
<Button size="small" danger icon={<DeleteOutlined/>}/>
|
|
257
|
+
</Popconfirm>
|
|
258
|
+
</Space>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<div>
|
|
265
|
+
<div
|
|
266
|
+
style={{display: 'flex', justifyContent: 'space-between', marginBottom: 16, gap: 12, flexWrap: 'wrap'}}>
|
|
267
|
+
<Space>
|
|
268
|
+
<Input.Search placeholder="搜索产品名称/编码" onSearch={v => {
|
|
269
|
+
setSearchKey(v);
|
|
270
|
+
setPageNum(1)
|
|
271
|
+
}} style={{width: 220}} allowClear/>
|
|
272
|
+
<Select placeholder="状态筛选" allowClear style={{width: 120}} onChange={v => {
|
|
273
|
+
setStatusFilter(v);
|
|
274
|
+
setPageNum(1)
|
|
275
|
+
}}>
|
|
276
|
+
<Option value="DRAFT">草稿</Option>
|
|
277
|
+
<Option value="PUBLISHED">已发布</Option>
|
|
278
|
+
<Option value="OFFLINE">已下架</Option>
|
|
279
|
+
<Option value="ARCHIVED">已归档</Option>
|
|
280
|
+
</Select>
|
|
281
|
+
</Space>
|
|
282
|
+
<Button type="primary" icon={<PlusOutlined/>} onClick={handleCreate}>新建产品</Button>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<Table
|
|
286
|
+
dataSource={dataSource}
|
|
287
|
+
columns={columns}
|
|
288
|
+
loading={loading}
|
|
289
|
+
rowKey="id"
|
|
290
|
+
pagination={{
|
|
291
|
+
current: pageNum,
|
|
292
|
+
pageSize,
|
|
293
|
+
total,
|
|
294
|
+
showSizeChanger: false,
|
|
295
|
+
showTotal: t => `共 ${t} 个产品`,
|
|
296
|
+
onChange: p => setPageNum(p),
|
|
297
|
+
}}
|
|
298
|
+
locale={{
|
|
299
|
+
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="还没有产品,点击新建开始创建"/>
|
|
300
|
+
}}
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
{/* 创建/编辑 Modal */}
|
|
304
|
+
<Modal
|
|
305
|
+
title={editingProduct ? `编辑产品: ${editingProduct.productName}` : '新建产品'}
|
|
306
|
+
open={modalVisible}
|
|
307
|
+
onOk={handleSubmit}
|
|
308
|
+
onCancel={() => setModalVisible(false)}
|
|
309
|
+
width={640}
|
|
310
|
+
confirmLoading={createLoading}
|
|
311
|
+
okText={editingProduct ? '保存' : '创建'}
|
|
312
|
+
>
|
|
313
|
+
<Form form={form} layout="vertical" size="middle">
|
|
314
|
+
<Row gutter={16}>
|
|
315
|
+
<Col span={12}>
|
|
316
|
+
<Form.Item name="productName" label="产品名称"
|
|
317
|
+
rules={[{required: true, message: '请输入产品名称'}]}>
|
|
318
|
+
<Input placeholder="如:智能客服助手"/>
|
|
319
|
+
</Form.Item>
|
|
320
|
+
</Col>
|
|
321
|
+
<Col span={12}>
|
|
322
|
+
<Form.Item name="productCode" label="产品编码"
|
|
323
|
+
rules={[{required: true, message: '请输入产品编码'}]}>
|
|
324
|
+
<Input placeholder="如 product-ai-assistant" disabled={!!editingProduct}/>
|
|
325
|
+
</Form.Item>
|
|
326
|
+
</Col>
|
|
327
|
+
</Row>
|
|
328
|
+
|
|
329
|
+
<Row gutter={16}>
|
|
330
|
+
<Col span={12}>
|
|
331
|
+
<Form.Item name="category" label="产品分类">
|
|
332
|
+
<Input placeholder="如 AI助手, 图像生成"/>
|
|
333
|
+
</Form.Item>
|
|
334
|
+
</Col>
|
|
335
|
+
<Col span={12}>
|
|
336
|
+
<Form.Item name="version" label="版本号" initialValue="1.0.0">
|
|
337
|
+
<Input placeholder="1.0.0"/>
|
|
338
|
+
</Form.Item>
|
|
339
|
+
</Col>
|
|
340
|
+
</Row>
|
|
341
|
+
|
|
342
|
+
<Form.Item name="description" label="产品描述">
|
|
343
|
+
<TextArea rows={2} placeholder="简短描述这个产品的能力和使用场景"/>
|
|
344
|
+
</Form.Item>
|
|
345
|
+
|
|
346
|
+
<Form.Item name="tags" label="产品标签">
|
|
347
|
+
<Input placeholder="用逗号分隔,如 AI,客服,对话"/>
|
|
348
|
+
</Form.Item>
|
|
349
|
+
|
|
350
|
+
<Form.Item name="iconUrl" label="图标URL">
|
|
351
|
+
<Input placeholder="可选,图标图片URL"/>
|
|
352
|
+
</Form.Item>
|
|
353
|
+
|
|
354
|
+
{!editingProduct && (
|
|
355
|
+
<Form.Item name="status" label="创建后状态" initialValue="DRAFT">
|
|
356
|
+
<Select>
|
|
357
|
+
<Option value="DRAFT">草稿(仅自己可见)</Option>
|
|
358
|
+
<Option value="PUBLISHED">直接发布</Option>
|
|
359
|
+
</Select>
|
|
360
|
+
</Form.Item>
|
|
361
|
+
)}
|
|
362
|
+
</Form>
|
|
363
|
+
</Modal>
|
|
364
|
+
|
|
365
|
+
{/* 详情 Drawer */}
|
|
366
|
+
<Drawer
|
|
367
|
+
title="产品详情"
|
|
368
|
+
open={detailVisible}
|
|
369
|
+
onClose={() => setDetailVisible(false)}
|
|
370
|
+
width={560}
|
|
371
|
+
>
|
|
372
|
+
{detailProduct && (
|
|
373
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
|
|
374
|
+
<div style={{display: 'flex', alignItems: 'center', gap: 12}}>
|
|
375
|
+
<div style={{
|
|
376
|
+
width: 48, height: 48, borderRadius: 10,
|
|
377
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
378
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
|
379
|
+
}}>
|
|
380
|
+
<ShopOutlined style={{fontSize: 22, color: '#fff'}}/>
|
|
381
|
+
</div>
|
|
382
|
+
<div>
|
|
383
|
+
<div style={{fontSize: 16, fontWeight: 600}}>{detailProduct.productName}</div>
|
|
384
|
+
<div style={{fontSize: 12, color: '#999'}}>{detailProduct.productCode}</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<Divider style={{margin: '8px 0'}}/>
|
|
389
|
+
|
|
390
|
+
<Row gutter={16}>
|
|
391
|
+
<Col span={12}><Statistic title="分类" value={detailProduct.category || '未分类'}
|
|
392
|
+
valueStyle={{fontSize: 14}}/></Col>
|
|
393
|
+
<Col span={12}><Statistic title="版本" value={detailProduct.version || '1.0.0'}
|
|
394
|
+
valueStyle={{fontSize: 14}}/></Col>
|
|
395
|
+
</Row>
|
|
396
|
+
|
|
397
|
+
<div>
|
|
398
|
+
<div style={{fontSize: 12, color: '#999', marginBottom: 4}}>状态</div>
|
|
399
|
+
<Badge status={
|
|
400
|
+
detailProduct.status === 'PUBLISHED' ? 'success' :
|
|
401
|
+
detailProduct.status === 'DRAFT' ? 'warning' : 'error'
|
|
402
|
+
} text={
|
|
403
|
+
detailProduct.status === 'PUBLISHED' ? '已发布' :
|
|
404
|
+
detailProduct.status === 'DRAFT' ? '草稿' :
|
|
405
|
+
detailProduct.status === 'OFFLINE' ? '已下架' : '已归档'
|
|
406
|
+
}/>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
{detailProduct.tags && (
|
|
410
|
+
<div>
|
|
411
|
+
<div style={{fontSize: 12, color: '#999', marginBottom: 6}}>标签</div>
|
|
412
|
+
<Space wrap>
|
|
413
|
+
{(detailProduct.tags || '').split(',').filter(Boolean).map((t, i) => (
|
|
414
|
+
<Tag key={i} color="blue">{t}</Tag>
|
|
415
|
+
))}
|
|
416
|
+
</Space>
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
<div>
|
|
421
|
+
<div style={{fontSize: 12, color: '#999', marginBottom: 4}}>描述</div>
|
|
422
|
+
<div
|
|
423
|
+
style={{color: '#333', lineHeight: 1.6}}>{detailProduct.description || '暂无描述'}</div>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
)}
|
|
427
|
+
</Drawer>
|
|
428
|
+
</div>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ===== 产品配置页 =====
|
|
433
|
+
const ProductConfigPage = () => {
|
|
434
|
+
const [products, setProducts] = useState([])
|
|
435
|
+
const [selectedProduct, setSelectedProduct] = useState(null)
|
|
436
|
+
const [configData, setConfigData] = useState({scenes: [], params: []})
|
|
437
|
+
const [loading, setLoading] = useState(false)
|
|
438
|
+
const [saving, setSaving] = useState(false)
|
|
439
|
+
const [sceneModalVisible, setSceneModalVisible] = useState(false)
|
|
440
|
+
const [form] = Form.useForm()
|
|
441
|
+
|
|
442
|
+
useEffect(() => {
|
|
443
|
+
productApi.page({id: 1, pageSize: 100}).then(res => {
|
|
444
|
+
setProducts(res.data?.records || [])
|
|
445
|
+
})
|
|
446
|
+
}, [])
|
|
447
|
+
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
if (!selectedProduct) return
|
|
450
|
+
loadConfig(selectedProduct.productCode)
|
|
451
|
+
}, [selectedProduct])
|
|
452
|
+
|
|
453
|
+
const loadConfig = async (productCode) => {
|
|
454
|
+
setLoading(true)
|
|
455
|
+
try {
|
|
456
|
+
const res = await productApi.configGet(productCode)
|
|
457
|
+
setConfigData(res.data || {scenes: [], params: []})
|
|
458
|
+
} catch (e) {
|
|
459
|
+
setConfigData({scenes: [], params: []})
|
|
460
|
+
}
|
|
461
|
+
setLoading(false)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const handleSave = async () => {
|
|
465
|
+
if (!selectedProduct) return
|
|
466
|
+
setSaving(true)
|
|
467
|
+
try {
|
|
468
|
+
await productApi.configSave({productCode: selectedProduct.productCode, ...configData})
|
|
469
|
+
message.success('配置已保存')
|
|
470
|
+
} catch (e) {
|
|
471
|
+
message.error('保存失败')
|
|
472
|
+
} finally {
|
|
473
|
+
setSaving(false)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const handleAddScene = () => {
|
|
478
|
+
form.resetFields()
|
|
479
|
+
setSceneModalVisible(true)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const handleSubmitScene = async () => {
|
|
483
|
+
try {
|
|
484
|
+
const values = await form.validateFields()
|
|
485
|
+
setConfigData(prev => ({
|
|
486
|
+
...prev,
|
|
487
|
+
scenes: [...(prev.scenes || []), {...values, id: Date.now()}]
|
|
488
|
+
}))
|
|
489
|
+
setSceneModalVisible(false)
|
|
490
|
+
message.success('场景已添加')
|
|
491
|
+
} catch (e) {
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const handleRemoveScene = (id) => {
|
|
496
|
+
setConfigData(prev => ({
|
|
497
|
+
...prev,
|
|
498
|
+
scenes: (prev.scenes || []).filter(s => s.id !== id)
|
|
499
|
+
}))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<div style={{display: 'flex', gap: 16}}>
|
|
504
|
+
{/* 左侧:产品选择 */}
|
|
505
|
+
<Card
|
|
506
|
+
size="small"
|
|
507
|
+
title={<span style={{fontWeight: 600}}><ShopOutlined style={{color: '#667eea', marginRight: 6}}/>选择产品</span>}
|
|
508
|
+
style={{width: 260, flexShrink: 0, borderRadius: 10}}
|
|
509
|
+
styles={{header: {borderBottom: '1px solid #f0f0f0', padding: '12px 16px'}, body: {padding: 8}}}
|
|
510
|
+
>
|
|
511
|
+
{(products || []).map(p => (
|
|
512
|
+
<div
|
|
513
|
+
key={p.id}
|
|
514
|
+
onClick={() => setSelectedProduct(p)}
|
|
515
|
+
style={{
|
|
516
|
+
padding: '10px 12px',
|
|
517
|
+
borderRadius: 8,
|
|
518
|
+
cursor: 'pointer',
|
|
519
|
+
marginBottom: 4,
|
|
520
|
+
background: selectedProduct?.id === p.id ? 'linear-gradient(135deg, #667eea22, #764ba222)' : 'transparent',
|
|
521
|
+
border: selectedProduct?.id === p.id ? '1px solid #667eea44' : '1px solid transparent',
|
|
522
|
+
transition: 'all 0.2s',
|
|
523
|
+
}}
|
|
524
|
+
>
|
|
525
|
+
<div style={{fontSize: 13, fontWeight: 500}}>{p.productName}</div>
|
|
526
|
+
<div style={{fontSize: 11, color: '#999'}}>{p.productCode}</div>
|
|
527
|
+
</div>
|
|
528
|
+
))}
|
|
529
|
+
{products.length === 0 && <Empty description="暂无产品" style={{padding: 20}}/>}
|
|
530
|
+
</Card>
|
|
531
|
+
|
|
532
|
+
{/* 右侧:配置面板 */}
|
|
533
|
+
<Card
|
|
534
|
+
title={<span
|
|
535
|
+
style={{fontWeight: 600}}>产品配置 {selectedProduct ? `- ${selectedProduct.productName}` : ''}</span>}
|
|
536
|
+
extra={<Button type="primary" icon={<CloudUploadOutlined/>} onClick={handleSave}
|
|
537
|
+
disabled={!selectedProduct} loading={saving}>保存配置</Button>}
|
|
538
|
+
style={{flex: 1, borderRadius: 10}}
|
|
539
|
+
>
|
|
540
|
+
{!selectedProduct ? (
|
|
541
|
+
<Empty description="请先选择产品" style={{padding: 60}} image={Empty.PRESENTED_IMAGE_SIMPLE}/>
|
|
542
|
+
) : loading ? (
|
|
543
|
+
<div style={{textAlign: 'center', padding: 40}}>加载中...</div>
|
|
544
|
+
) : (
|
|
545
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 20}}>
|
|
546
|
+
{/* 场景列表 */}
|
|
547
|
+
<div>
|
|
548
|
+
<div style={{
|
|
549
|
+
display: 'flex',
|
|
550
|
+
justifyContent: 'space-between',
|
|
551
|
+
alignItems: 'center',
|
|
552
|
+
marginBottom: 12
|
|
553
|
+
}}>
|
|
554
|
+
<span style={{fontWeight: 500}}>关联场景</span>
|
|
555
|
+
<Button size="small" icon={<PlusOutlined/>} onClick={handleAddScene}>添加场景</Button>
|
|
556
|
+
</div>
|
|
557
|
+
{(configData.scenes || []).length === 0 ? (
|
|
558
|
+
<div style={{
|
|
559
|
+
textAlign: 'center',
|
|
560
|
+
padding: '20px 0',
|
|
561
|
+
color: '#999',
|
|
562
|
+
background: '#fafafa',
|
|
563
|
+
borderRadius: 8
|
|
564
|
+
}}>
|
|
565
|
+
<ExclamationCircleOutlined style={{marginRight: 6}}/>暂无关联场景
|
|
566
|
+
</div>
|
|
567
|
+
) : (
|
|
568
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 8}}>
|
|
569
|
+
{(configData.scenes || []).map((scene, idx) => (
|
|
570
|
+
<Card key={scene.id || idx} size="small"
|
|
571
|
+
style={{background: '#fafafa', border: 'none'}}>
|
|
572
|
+
<div style={{
|
|
573
|
+
display: 'flex',
|
|
574
|
+
justifyContent: 'space-between',
|
|
575
|
+
alignItems: 'center'
|
|
576
|
+
}}>
|
|
577
|
+
<div>
|
|
578
|
+
<Tag color="purple">{scene.sceneName}</Tag>
|
|
579
|
+
<span style={{
|
|
580
|
+
fontSize: 12,
|
|
581
|
+
color: '#666',
|
|
582
|
+
marginLeft: 8
|
|
583
|
+
}}>{scene.sceneCode}</span>
|
|
584
|
+
</div>
|
|
585
|
+
<Button size="small" danger icon={<DeleteOutlined/>}
|
|
586
|
+
onClick={() => handleRemoveScene(scene.id)}/>
|
|
587
|
+
</div>
|
|
588
|
+
</Card>
|
|
589
|
+
))}
|
|
590
|
+
</div>
|
|
591
|
+
)}
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
)}
|
|
595
|
+
</Card>
|
|
596
|
+
|
|
597
|
+
{/* 添加场景 Modal */}
|
|
598
|
+
<Modal
|
|
599
|
+
title="添加场景"
|
|
600
|
+
open={sceneModalVisible}
|
|
601
|
+
onOk={handleSubmitScene}
|
|
602
|
+
onCancel={() => setSceneModalVisible(false)}
|
|
603
|
+
okText="添加"
|
|
604
|
+
>
|
|
605
|
+
<Form form={form} layout="vertical">
|
|
606
|
+
<Form.Item name="sceneName" label="场景名称" rules={[{required: true, message: '请输入场景名称'}]}>
|
|
607
|
+
<Input placeholder="如 智能问答"/>
|
|
608
|
+
</Form.Item>
|
|
609
|
+
<Form.Item name="sceneCode" label="场景编码" rules={[{required: true, message: '请输入场景编码'}]}>
|
|
610
|
+
<Input placeholder="如 chat_scene"/>
|
|
611
|
+
</Form.Item>
|
|
612
|
+
<Form.Item name="sceneType" label="场景类型" initialValue="CONVERSATION">
|
|
613
|
+
<Select>
|
|
614
|
+
<Option value="CONVERSATION">对话</Option>
|
|
615
|
+
<Option value="TASK">任务</Option>
|
|
616
|
+
<Option value="WORKFLOW">工作流</Option>
|
|
617
|
+
</Select>
|
|
618
|
+
</Form.Item>
|
|
619
|
+
<Form.Item name="description" label="描述">
|
|
620
|
+
<TextArea rows={2} placeholder="场景描述"/>
|
|
621
|
+
</Form.Item>
|
|
622
|
+
</Form>
|
|
623
|
+
</Modal>
|
|
624
|
+
</div>
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export default ProductListPage
|