@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.
Files changed (41) hide show
  1. package/README.md +66 -0
  2. package/dist/z-agent-frontend-component.css +1 -0
  3. package/dist/z-agent-frontend-component.es.js +9956 -0
  4. package/dist/z-agent-frontend-component.umd.js +219 -0
  5. package/package.json +77 -0
  6. package/src/api/apiRouter.js +78 -0
  7. package/src/api/index.js +23 -0
  8. package/src/api/request.js +59 -0
  9. package/src/api/routes.js +140 -0
  10. package/src/dev.jsx +80 -0
  11. package/src/index.js +86 -0
  12. package/src/pages/agent/app/index.jsx +2 -0
  13. package/src/pages/agent/editor/AgentAppEditor.jsx +456 -0
  14. package/src/pages/agent/editor/WorkflowEditor.jsx +495 -0
  15. package/src/pages/agent/editor/nodes/index.ts +225 -0
  16. package/src/pages/agent/index.jsx +1379 -0
  17. package/src/pages/agent/share.jsx +512 -0
  18. package/src/pages/ak/AkUsageDrawer.jsx +208 -0
  19. package/src/pages/ak/index.jsx +496 -0
  20. package/src/pages/llm/index.jsx +736 -0
  21. package/src/pages/llm/model/index.jsx +220 -0
  22. package/src/pages/llm/provider/index.jsx +173 -0
  23. package/src/pages/mcp/index.jsx +359 -0
  24. package/src/pages/oss/BucketList.jsx +320 -0
  25. package/src/pages/oss/ObjectBrowser.jsx +409 -0
  26. package/src/pages/product/execute.jsx +608 -0
  27. package/src/pages/product/index.jsx +628 -0
  28. package/src/pages/product/scene.jsx +746 -0
  29. package/src/pages/script/ApiBridgeEditor.jsx +255 -0
  30. package/src/pages/script/CurlImportModal.jsx +263 -0
  31. package/src/pages/script/FieldMappingEditor.jsx +131 -0
  32. package/src/pages/script/OpenApiImportModal.jsx +212 -0
  33. package/src/pages/script/index.jsx +532 -0
  34. package/src/pages/skill/index.jsx +1595 -0
  35. package/src/pages/trace/DebugPlayground.jsx +357 -0
  36. package/src/pages/trace/components/MetricsDashboard.jsx +164 -0
  37. package/src/pages/trace/components/RagFragments.jsx +134 -0
  38. package/src/pages/trace/components/Timeline.jsx +142 -0
  39. package/src/pages/trace/components/ToolCallTree.jsx +116 -0
  40. package/src/pages/trace/index.jsx +13 -0
  41. package/src/pages/usage/index.jsx +352 -0
@@ -0,0 +1,496 @@
1
+ import {useEffect, useState} from 'react'
2
+ import {
3
+ Alert,
4
+ Button,
5
+ Card,
6
+ Col,
7
+ Form,
8
+ Input,
9
+ message,
10
+ Modal,
11
+ Radio,
12
+ Row,
13
+ Select,
14
+ Space,
15
+ Spin,
16
+ Statistic,
17
+ Table,
18
+ Tabs,
19
+ Tag,
20
+ Tooltip
21
+ } from 'antd'
22
+ import {
23
+ ApiOutlined,
24
+ AppstoreOutlined,
25
+ CheckCircleOutlined,
26
+ CopyOutlined,
27
+ DeleteOutlined,
28
+ KeyOutlined,
29
+ PlusOutlined,
30
+ ReloadOutlined,
31
+ StopOutlined,
32
+ ThunderboltOutlined,
33
+ UserOutlined
34
+ } from '@ant-design/icons'
35
+ import request from '../../api'
36
+ import AkUsageDrawer from './AkUsageDrawer'
37
+
38
+ const {confirm} = Modal
39
+
40
+ const API_BASE_URL = 'http://localhost:8888'
41
+
42
+ const formatTokens = (n) => {
43
+ if (!n) return '0'
44
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'
45
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K'
46
+ return Number(n).toLocaleString()
47
+ }
48
+
49
+ const AkManage = () => {
50
+ const [activeTab, setActiveTab] = useState('PERSONAL') // PERSONAL / APP
51
+ const [keys, setKeys] = useState([])
52
+ const [loading, setLoading] = useState(false)
53
+ const [visible, setVisible] = useState(false)
54
+ const [editing, setEditing] = useState(null)
55
+ const [form] = Form.useForm()
56
+ const [showSkModal, setShowSkModal] = useState(null)
57
+ const [drawerAk, setDrawerAk] = useState(null)
58
+ const [appOptions, setAppOptions] = useState([])
59
+
60
+ const fetchList = async () => {
61
+ setLoading(true)
62
+ try {
63
+ const res = await request('/ak/list', {method: 'GET', params: {akType: activeTab}})
64
+ setKeys(Array.isArray(res) ? res : (res?.data || []))
65
+ } catch (e) {
66
+ message.error('加载失败')
67
+ } finally {
68
+ setLoading(false)
69
+ }
70
+ }
71
+
72
+ const fetchApps = async () => {
73
+ try {
74
+ const res = await request('/agent/app/page', {
75
+ method: 'POST',
76
+ body: JSON.stringify({current: 1, size: 100})
77
+ })
78
+ const list = Array.isArray(res) ? res : (res?.records || res?.data || [])
79
+ setAppOptions(list.map(a => ({value: a.appCode, label: a.appName || a.appCode})))
80
+ } catch (e) {
81
+ // 静默失败
82
+ }
83
+ }
84
+
85
+ useEffect(() => {
86
+ fetchList()
87
+ }, [activeTab])
88
+ useEffect(() => {
89
+ fetchApps()
90
+ }, [])
91
+
92
+ const handleAdd = () => {
93
+ setEditing(null)
94
+ form.resetFields()
95
+ form.setFieldsValue({akType: activeTab}) // 按当前 Tab 预填类型
96
+ setVisible(true)
97
+ }
98
+
99
+ const handleEdit = (record) => {
100
+ setEditing(record)
101
+ form.setFieldsValue({
102
+ akName: record.akName,
103
+ description: record.description,
104
+ akType: record.akType || 'PERSONAL',
105
+ appCode: record.appCode,
106
+ appName: record.appName,
107
+ })
108
+ setVisible(true)
109
+ }
110
+
111
+ const handleDelete = (record) => {
112
+ confirm({
113
+ title: '确认删除',
114
+ content: `删除 AK「${record.akName}」?此操作不可恢复。`,
115
+ okText: '删除',
116
+ okType: 'danger',
117
+ onOk: async () => {
118
+ await request('/ak/delete', {method: 'POST', params: {id: record.id}})
119
+ message.success('删除成功')
120
+ fetchList()
121
+ },
122
+ })
123
+ }
124
+
125
+ const handleToggle = async (record) => {
126
+ const newStatus = record.status === 1 ? 0 : 1
127
+ await request('/ak/toggle', {method: 'POST', params: {id: record.id, status: newStatus}})
128
+ message.success(newStatus === 1 ? '已启用' : '已禁用')
129
+ fetchList()
130
+ }
131
+
132
+ const handleSubmit = async () => {
133
+ try {
134
+ const values = await form.validateFields()
135
+ if (editing) {
136
+ await request('/ak/update', {method: 'POST', data: {...values, id: editing.id}})
137
+ message.success('更新成功')
138
+ setVisible(false)
139
+ fetchList()
140
+ } else {
141
+ const res = await request('/ak/create', {method: 'POST', data: values})
142
+ const data = res?.accessKey ? res : (res?.data || {})
143
+ setVisible(false)
144
+ fetchList()
145
+ setShowSkModal({
146
+ akName: values.akName,
147
+ accessKey: data.accessKey,
148
+ secretKey: data.secretKey,
149
+ })
150
+ }
151
+ } catch (e) {
152
+ // 校验失败不弹错误(form 自己展示)
153
+ }
154
+ }
155
+
156
+ const handleCopy = (text, label) => {
157
+ navigator.clipboard.writeText(text)
158
+ message.success(`${label} 已复制`)
159
+ }
160
+
161
+ const columns = [
162
+ {
163
+ title: '名称',
164
+ dataIndex: 'akName',
165
+ key: 'akName',
166
+ width: 160,
167
+ render: (text, record) => (
168
+ <Space>
169
+ <KeyOutlined style={{color: '#1677ff'}}/>
170
+ <span style={{fontWeight: 500}}>{text}</span>
171
+ {record.akType === 'APP' && record.appName && (
172
+ <Tag color="purple" style={{borderRadius: 10, fontSize: 11}}>{record.appName}</Tag>
173
+ )}
174
+ </Space>
175
+ ),
176
+ },
177
+ {
178
+ title: 'AccessKey',
179
+ dataIndex: 'accessKey',
180
+ key: 'accessKey',
181
+ width: 200,
182
+ render: (text) => (
183
+ <Space>
184
+ <code style={{
185
+ fontFamily: 'monospace', fontSize: 12, color: '#595959',
186
+ background: '#f5f5f5', padding: '3px 8px', borderRadius: 4,
187
+ }}>
188
+ {text}
189
+ </code>
190
+ <Tooltip title="复制 AccessKey">
191
+ <Button size="small" type="text" icon={<CopyOutlined/>} onClick={(e) => {
192
+ e.stopPropagation();
193
+ handleCopy(text, 'AccessKey')
194
+ }}/>
195
+ </Tooltip>
196
+ </Space>
197
+ ),
198
+ },
199
+ {
200
+ title: 'Token 消耗',
201
+ dataIndex: 'totalTokens',
202
+ key: 'totalTokens',
203
+ width: 110,
204
+ sorter: (a, b) => (a.totalTokens || 0) - (b.totalTokens || 0),
205
+ render: (t) => (
206
+ <Space>
207
+ <ThunderboltOutlined style={{color: '#faad14', fontSize: 12}}/>
208
+ <span style={{fontWeight: 500}}>{formatTokens(t)}</span>
209
+ </Space>
210
+ ),
211
+ },
212
+ {
213
+ title: '最后使用',
214
+ dataIndex: 'lastUsedTime',
215
+ key: 'lastUsedTime',
216
+ width: 160,
217
+ render: (t) => (
218
+ <Space size={4}>
219
+ <span style={{color: t ? '#595959' : '#bfbfbf', fontSize: 13}}>{t || '从未使用'}</span>
220
+ </Space>
221
+ ),
222
+ },
223
+ {
224
+ title: '状态',
225
+ dataIndex: 'status',
226
+ key: 'status',
227
+ width: 70,
228
+ render: (s) => (
229
+ <Tag color={s === 1 ? 'green' : 'red'} style={{borderRadius: 20}}>
230
+ {s === 1 ? '正常' : '禁用'}
231
+ </Tag>
232
+ ),
233
+ },
234
+ {
235
+ title: '描述',
236
+ dataIndex: 'description',
237
+ key: 'description',
238
+ ellipsis: true,
239
+ },
240
+ {
241
+ title: '创建时间',
242
+ dataIndex: 'createdTime',
243
+ key: 'createdTime',
244
+ width: 160,
245
+ },
246
+ {
247
+ title: '操作',
248
+ key: 'action',
249
+ width: 130,
250
+ render: (_, record) => (
251
+ <Space size="small" onClick={(e) => e.stopPropagation()}>
252
+ <Tooltip title={record.status === 1 ? '禁用' : '启用'}>
253
+ <Button
254
+ size="small"
255
+ type="text"
256
+ icon={record.status === 1 ? <StopOutlined style={{color: '#faad14'}}/> :
257
+ <CheckCircleOutlined style={{color: '#52c41a'}}/>}
258
+ onClick={() => handleToggle(record)}
259
+ />
260
+ </Tooltip>
261
+ <Tooltip title="编辑">
262
+ <Button size="small" type="text" icon={<KeyOutlined/>} onClick={() => handleEdit(record)}/>
263
+ </Tooltip>
264
+ <Tooltip title="删除">
265
+ <Button size="small" type="text" icon={<DeleteOutlined style={{color: '#ff4d4f'}}/>}
266
+ onClick={() => handleDelete(record)}/>
267
+ </Tooltip>
268
+ </Space>
269
+ ),
270
+ },
271
+ ]
272
+
273
+ const totalTokensAll = keys.reduce((s, k) => s + (k.totalTokens || 0), 0)
274
+ const activeCount = keys.filter(k => k.status === 1).length
275
+
276
+ return (
277
+ <div style={{padding: 16, display: 'flex', flexDirection: 'column', gap: 12}}>
278
+ <Alert
279
+ type="info"
280
+ showIcon
281
+ icon={<ApiOutlined/>}
282
+ message={
283
+ <Space>
284
+ <span style={{fontWeight: 500}}>API 接入地址:</span>
285
+ <code style={{
286
+ fontFamily: 'monospace', fontSize: 13, background: '#e6f4ff',
287
+ padding: '2px 8px', borderRadius: 4,
288
+ }}>{API_BASE_URL}</code>
289
+ <Button size="small" type="text" icon={<CopyOutlined/>}
290
+ onClick={() => handleCopy(API_BASE_URL, 'API 地址')}/>
291
+ <span style={{color: '#8c8c8c', fontSize: 12}}>使用此地址 + AccessKey 调用平台所有模型</span>
292
+ </Space>
293
+ }
294
+ style={{border: 'none', background: '#e6f4ff'}}
295
+ />
296
+
297
+ <Row gutter={12}>
298
+ <Col span={6}>
299
+ <Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
300
+ <Statistic title="AK 总数" value={keys.length} prefix={<KeyOutlined/>}
301
+ valueStyle={{fontSize: 18}}/>
302
+ </Card>
303
+ </Col>
304
+ <Col span={6}>
305
+ <Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
306
+ <Statistic title="累计 Token" value={formatTokens(totalTokensAll)}
307
+ prefix={<ThunderboltOutlined/>} valueStyle={{fontSize: 18}}/>
308
+ </Card>
309
+ </Col>
310
+ <Col span={6}>
311
+ <Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
312
+ <Statistic title="有效 AK" value={activeCount}
313
+ prefix={<CheckCircleOutlined style={{color: '#52c41a'}}/>}
314
+ valueStyle={{fontSize: 18}}/>
315
+ </Card>
316
+ </Col>
317
+ <Col span={6}>
318
+ <Card size="small" style={{background: '#f5f5f5'}} bodyStyle={{padding: '10px 14px'}}>
319
+ <Statistic title="所属应用" value={new Set(keys.map(k => k.appCode).filter(Boolean)).size}
320
+ prefix={<AppstoreOutlined/>} valueStyle={{fontSize: 18}}/>
321
+ </Card>
322
+ </Col>
323
+ </Row>
324
+
325
+ <Card size="small" styles={{body: {padding: 0}}}>
326
+ <Tabs
327
+ activeKey={activeTab}
328
+ onChange={setActiveTab}
329
+ size="small"
330
+ tabBarStyle={{margin: 0, padding: '0 16px'}}
331
+ tabBarExtraContent={
332
+ <Space style={{padding: '8px 0'}}>
333
+ <Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd} size="small">
334
+ {activeTab === 'PERSONAL' ? '申请个人 AK' : '申请应用 AK'}
335
+ </Button>
336
+ <Button icon={<ReloadOutlined/>} onClick={fetchList} size="small"/>
337
+ </Space>
338
+ }
339
+ items={[
340
+ {
341
+ key: 'PERSONAL',
342
+ label: <Space><UserOutlined/>个人 AK</Space>,
343
+ children: (
344
+ <Spin spinning={loading}>
345
+ <Table
346
+ dataSource={keys}
347
+ columns={columns}
348
+ rowKey="id"
349
+ pagination={false}
350
+ size="small"
351
+ onRow={(record) => ({
352
+ onClick: () => setDrawerAk(record),
353
+ style: {cursor: 'pointer'},
354
+ })}
355
+ locale={{emptyText: '暂无个人 AK,点右上角"申请个人 AK"创建'}}
356
+ />
357
+ </Spin>
358
+ ),
359
+ },
360
+ {
361
+ key: 'APP',
362
+ label: <Space><AppstoreOutlined/>应用 AK</Space>,
363
+ children: (
364
+ <Spin spinning={loading}>
365
+ <Table
366
+ dataSource={keys}
367
+ columns={columns}
368
+ rowKey="id"
369
+ pagination={false}
370
+ size="small"
371
+ onRow={(record) => ({
372
+ onClick: () => setDrawerAk(record),
373
+ style: {cursor: 'pointer'},
374
+ })}
375
+ locale={{emptyText: '暂无应用 AK,点右上角"申请应用 AK"并绑定一个应用'}}
376
+ />
377
+ </Spin>
378
+ ),
379
+ },
380
+ ]}
381
+ />
382
+ </Card>
383
+
384
+ {/* 创建/编辑弹窗 */}
385
+ <Modal title={editing ? '编辑 AK' : (activeTab === 'APP' ? '申请应用 AK' : '申请个人 AK')}
386
+ open={visible}
387
+ onOk={handleSubmit}
388
+ onCancel={() => setVisible(false)}
389
+ width={520}
390
+ okText={editing ? '保存' : '申请'}
391
+ destroyOnClose>
392
+ <Form form={form} layout="vertical" preserve={false}>
393
+ <Form.Item name="akType" label="AK 类型" rules={[{required: true}]}>
394
+ <Radio.Group disabled={!!editing}>
395
+ <Radio.Button value="PERSONAL">个人 AK</Radio.Button>
396
+ <Radio.Button value="APP">应用 AK</Radio.Button>
397
+ </Radio.Group>
398
+ </Form.Item>
399
+ <Form.Item shouldUpdate={(prev, curr) => prev.akType !== curr.akType} noStyle>
400
+ {() => form.getFieldValue('akType') === 'APP' && (
401
+ <Form.Item name="appCode" label="所属应用"
402
+ rules={[{required: true, message: '应用 AK 必须绑定一个应用'}]}>
403
+ <Select
404
+ showSearch
405
+ optionFilterProp="label"
406
+ placeholder="选择应用"
407
+ options={appOptions}
408
+ />
409
+ </Form.Item>
410
+ )}
411
+ </Form.Item>
412
+ <Form.Item name="akName" label="名称" rules={[{required: true, message: '请输入 AK 名称'}]}>
413
+ <Input placeholder={activeTab === 'APP' ? '如:生产环境-订单服务' : '如:本地调试'}/>
414
+ </Form.Item>
415
+ <Form.Item name="description" label="描述">
416
+ <Input.TextArea rows={2} placeholder="密钥用途..."/>
417
+ </Form.Item>
418
+ {!editing && (
419
+ <Alert
420
+ type="info"
421
+ showIcon
422
+ message="AccessKey 和 SecretKey 将由系统自动生成。SecretKey 仅在创建时展示一次,请妥善保管。"
423
+ style={{background: '#e6f4ff', border: 'none'}}
424
+ />
425
+ )}
426
+ </Form>
427
+ </Modal>
428
+
429
+ {/* SecretKey 一次性展示弹窗 */}
430
+ <Modal
431
+ title="AK 创建成功"
432
+ open={!!showSkModal}
433
+ onCancel={() => setShowSkModal(null)}
434
+ footer={
435
+ <Button type="primary" onClick={() => setShowSkModal(null)}>
436
+ 我已保存,关闭
437
+ </Button>
438
+ }
439
+ width={520}
440
+ >
441
+ {showSkModal && (
442
+ <div>
443
+ <Alert
444
+ type="warning"
445
+ showIcon
446
+ message="SecretKey 仅在此展示一次,关闭后将无法再次查看。请立即复制并妥善保管。"
447
+ style={{marginBottom: 16}}
448
+ />
449
+
450
+ <div style={{marginBottom: 12}}>
451
+ <div style={{fontSize: 12, color: '#8c8c8c', marginBottom: 4}}>AccessKey</div>
452
+ <div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
453
+ <code style={{
454
+ flex: 1, fontFamily: 'monospace', fontSize: 14, padding: '8px 12px',
455
+ background: '#f5f5f5', borderRadius: 6, border: '1px solid #d9d9d9',
456
+ }}>
457
+ {showSkModal.accessKey}
458
+ </code>
459
+ <Button icon={<CopyOutlined/>}
460
+ onClick={() => handleCopy(showSkModal.accessKey, 'AccessKey')}>复制</Button>
461
+ </div>
462
+ </div>
463
+
464
+ <div style={{marginBottom: 12}}>
465
+ <div style={{fontSize: 12, color: '#8c8c8c', marginBottom: 4}}>SecretKey</div>
466
+ <div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
467
+ <code style={{
468
+ flex: 1, fontFamily: 'monospace', fontSize: 14, padding: '8px 12px',
469
+ background: '#fff7e6', borderRadius: 6, border: '1px solid #ffd591',
470
+ color: '#d46b08',
471
+ }}>
472
+ {showSkModal.secretKey}
473
+ </code>
474
+ <Button type="primary" icon={<CopyOutlined/>}
475
+ onClick={() => handleCopy(showSkModal.secretKey, 'SecretKey')}>复制</Button>
476
+ </div>
477
+ </div>
478
+
479
+ <div style={{fontSize: 12, color: '#ff4d4f', marginTop: 8}}>
480
+ ⚠ 请立即复制 SecretKey,此弹窗关闭后将无法再次获取。如遗失,请删除后重新申请。
481
+ </div>
482
+ </div>
483
+ )}
484
+ </Modal>
485
+
486
+ {/* 用量详情抽屉 */}
487
+ <AkUsageDrawer
488
+ open={!!drawerAk}
489
+ ak={drawerAk}
490
+ onClose={() => setDrawerAk(null)}
491
+ />
492
+ </div>
493
+ )
494
+ }
495
+
496
+ export default AkManage