@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,409 @@
1
+ import {useCallback, useEffect, useRef, useState} from 'react'
2
+ import {useNavigate, useParams} from 'react-router-dom'
3
+ import {
4
+ Breadcrumb,
5
+ Button,
6
+ Card,
7
+ Col,
8
+ Dropdown,
9
+ Empty,
10
+ Form,
11
+ Input,
12
+ message,
13
+ Modal,
14
+ Popconfirm,
15
+ Row,
16
+ Space,
17
+ Spin,
18
+ Statistic,
19
+ Table,
20
+ Tag,
21
+ Upload
22
+ } from 'antd'
23
+ import {
24
+ ArrowLeftOutlined,
25
+ CloudOutlined,
26
+ CopyOutlined,
27
+ DeleteOutlined,
28
+ DownloadOutlined,
29
+ FileImageOutlined,
30
+ FileOutlined,
31
+ FileTextOutlined,
32
+ FileZipOutlined,
33
+ FolderAddOutlined,
34
+ FolderOutlined,
35
+ MoreOutlined,
36
+ ReloadOutlined,
37
+ UploadOutlined
38
+ } from '@ant-design/icons'
39
+ import {ossApi} from '../../api'
40
+
41
+ const fmtSize = (size) => {
42
+ if (!size || size === 0) return '-'
43
+ const u = ['B', 'KB', 'MB', 'GB', 'TB']
44
+ let i = 0, n = size
45
+ while (n >= 1024 && i < u.length - 1) {
46
+ n /= 1024;
47
+ i++
48
+ }
49
+ return n.toFixed(i === 0 ? 0 : 1) + ' ' + u[i]
50
+ }
51
+
52
+ const fileIcon = (key) => {
53
+ if (key.endsWith('/')) return <FolderOutlined style={{color: '#faad14'}}/>
54
+ const ext = key.split('.').pop()?.toLowerCase()
55
+ if (['zip', 'tar', 'gz', 'rar', '7z'].includes(ext)) return <FileZipOutlined style={{color: '#722ed1'}}/>
56
+ if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) return <FileImageOutlined
57
+ style={{color: '#13c2c2'}}/>
58
+ if (['md', 'txt', 'log', 'json', 'xml', 'yaml', 'yml'].includes(ext)) return <FileTextOutlined
59
+ style={{color: '#52c41a'}}/>
60
+ return <FileOutlined style={{color: '#8c8c8c'}}/>
61
+ }
62
+
63
+ export default function ObjectBrowser() {
64
+ const {bucketName} = useParams()
65
+ const decodedBucket = decodeURIComponent(bucketName || '')
66
+ const navigate = useNavigate()
67
+
68
+ const [objects, setObjects] = useState([])
69
+ const [loading, setLoading] = useState(false)
70
+ const [currentPrefix, setCurrentPrefix] = useState('')
71
+ const [stats, setStats] = useState({objectCount: 0, totalSize: 0})
72
+ const [searchKey, setSearchKey] = useState('')
73
+ const [selectedRowKeys, setSelectedRowKeys] = useState([])
74
+ const [uploadLoading, setUploadLoading] = useState(false)
75
+ const [folderModalOpen, setFolderModalOpen] = useState(false)
76
+ const [folderForm] = Form.useForm()
77
+ const fileInputRef = useRef(null)
78
+
79
+ const loadObjects = useCallback(async (prefix = currentPrefix) => {
80
+ if (!decodedBucket) return
81
+ setLoading(true)
82
+ try {
83
+ const res = await ossApi.listObjects(decodedBucket, prefix || undefined)
84
+ const list = Array.isArray(res) ? res : (res?.data || [])
85
+ setObjects(list)
86
+ } catch (e) {
87
+ message.error('加载对象失败:' + (e?.message || '未知错误'))
88
+ setObjects([])
89
+ } finally {
90
+ setLoading(false)
91
+ }
92
+ }, [decodedBucket, currentPrefix])
93
+
94
+ const loadStats = useCallback(async () => {
95
+ try {
96
+ const res = await ossApi.getBucketStats(decodedBucket)
97
+ setStats(res || {objectCount: 0, totalSize: 0})
98
+ } catch (e) {
99
+ // 静默
100
+ }
101
+ }, [decodedBucket])
102
+
103
+ useEffect(() => {
104
+ setCurrentPrefix('')
105
+ setSelectedRowKeys([])
106
+ }, [decodedBucket])
107
+
108
+ useEffect(() => {
109
+ loadObjects(currentPrefix)
110
+ loadStats()
111
+ }, [currentPrefix, loadObjects, loadStats])
112
+
113
+ const handleEnterFolder = (key) => {
114
+ if (key.endsWith('/')) {
115
+ setCurrentPrefix(key)
116
+ setSelectedRowKeys([])
117
+ }
118
+ }
119
+
120
+ const handleBreadcrumb = (prefix) => {
121
+ setCurrentPrefix(prefix)
122
+ setSelectedRowKeys([])
123
+ }
124
+
125
+ const handleUpload = async (file) => {
126
+ if (!file) return false
127
+ setUploadLoading(true)
128
+ const key = (currentPrefix || '') + file.name
129
+ try {
130
+ await ossApi.uploadObject(decodedBucket, key, file)
131
+ message.success(`已上传: ${file.name}`)
132
+ loadObjects(currentPrefix)
133
+ loadStats()
134
+ } catch (e) {
135
+ message.error('上传失败:' + (e?.message || '未知错误'))
136
+ } finally {
137
+ setUploadLoading(false)
138
+ }
139
+ return false // 阻止 antd Upload 默认行为
140
+ }
141
+
142
+ const handleDelete = async (keys) => {
143
+ if (!keys || keys.length === 0) return
144
+ try {
145
+ if (keys.length === 1) {
146
+ await ossApi.deleteObject(decodedBucket, keys[0])
147
+ } else {
148
+ await ossApi.batchDelete(decodedBucket, keys)
149
+ }
150
+ message.success(`已删除 ${keys.length} 个`)
151
+ setSelectedRowKeys([])
152
+ loadObjects(currentPrefix)
153
+ loadStats()
154
+ } catch (e) {
155
+ message.error('删除失败:' + (e?.message || '未知错误'))
156
+ }
157
+ }
158
+
159
+ const handleCreateFolder = async () => {
160
+ try {
161
+ const vals = await folderForm.validateFields()
162
+ const key = (currentPrefix || '') + vals.name + '/'
163
+ await ossApi.createFolder(decodedBucket, key)
164
+ message.success('文件夹已创建')
165
+ setFolderModalOpen(false)
166
+ folderForm.resetFields()
167
+ loadObjects(currentPrefix)
168
+ } catch (e) {
169
+ if (e?.errorFields) return
170
+ message.error('创建失败:' + (e?.message || '未知错误'))
171
+ }
172
+ }
173
+
174
+ const handleDownload = async (key) => {
175
+ // 阿里云模式:走签名 URL(流量不经过主服务)
176
+ // local 模式:直接走 /api/v1/object/{bucket}/{key}
177
+ try {
178
+ const res = await ossApi.presignedUrl(decodedBucket, key, 3600)
179
+ const url = res?.url || ossApi.downloadUrl(decodedBucket, key)
180
+ window.open(url, '_blank')
181
+ } catch (e) {
182
+ // 降级:直接打开 GET URL
183
+ window.open(ossApi.downloadUrl(decodedBucket, key), '_blank')
184
+ }
185
+ }
186
+
187
+ const handleCopy = async (key) => {
188
+ // 简化版:复制到同桶 _copy/ 前缀
189
+ const newKey = (currentPrefix || '') + '_copy_' + key.split('/').pop()
190
+ try {
191
+ await ossApi.copyObject(decodedBucket, key, decodedBucket, newKey)
192
+ message.success('已复制')
193
+ loadObjects(currentPrefix)
194
+ } catch (e) {
195
+ message.error('复制失败:' + (e?.message || '未知错误'))
196
+ }
197
+ }
198
+
199
+ // 客户端搜索过滤
200
+ const filtered = searchKey
201
+ ? objects.filter(o => o.key?.toLowerCase().includes(searchKey.toLowerCase()))
202
+ : objects
203
+
204
+ const columns = [
205
+ {
206
+ title: '名称',
207
+ dataIndex: 'key',
208
+ render: (key, record) => {
209
+ const isFolder = key?.endsWith('/') || record.folder
210
+ const name = key?.split('/').filter(Boolean).pop() || key
211
+ return (
212
+ <span
213
+ style={{cursor: isFolder ? 'pointer' : 'default', color: isFolder ? '#faad14' : '#262626'}}
214
+ onClick={() => isFolder && handleEnterFolder(key)}
215
+ >
216
+ <Space>
217
+ {fileIcon(key)}
218
+ <span style={{fontWeight: isFolder ? 500 : 400}}>{name}</span>
219
+ </Space>
220
+ </span>
221
+ )
222
+ },
223
+ },
224
+ {title: '大小', dataIndex: 'size', width: 110, render: (s) => fmtSize(s)},
225
+ {title: '类型', dataIndex: 'contentType', width: 200, ellipsis: true, render: (t) => t ? <Tag>{t}</Tag> : '-'},
226
+ {
227
+ title: '修改时间',
228
+ dataIndex: 'lastModified',
229
+ width: 180,
230
+ render: (t) => t ? new Date(t).toLocaleString() : '-'
231
+ },
232
+ {
233
+ title: '操作', width: 160, fixed: 'right',
234
+ render: (_, record) => {
235
+ const isFolder = record.key?.endsWith('/') || record.folder
236
+ const items = isFolder
237
+ ? [
238
+ {key: 'enter', icon: <FolderOutlined/>, label: '进入'},
239
+ {key: 'delete', icon: <DeleteOutlined/>, label: '删除', danger: true},
240
+ ]
241
+ : [
242
+ {key: 'download', icon: <DownloadOutlined/>, label: '下载'},
243
+ {key: 'copy', icon: <CopyOutlined/>, label: '复制'},
244
+ {key: 'delete', icon: <DeleteOutlined/>, label: '删除', danger: true},
245
+ ]
246
+ return (
247
+ <Dropdown
248
+ trigger={['click']}
249
+ menu={{
250
+ items,
251
+ onClick: ({key, domEvent}) => {
252
+ domEvent.stopPropagation()
253
+ if (key === 'enter') handleEnterFolder(record.key)
254
+ else if (key === 'download') handleDownload(record.key)
255
+ else if (key === 'copy') handleCopy(record.key)
256
+ else if (key === 'delete') handleDelete([record.key])
257
+ },
258
+ }}
259
+ >
260
+ <Button type="text" size="small" icon={<MoreOutlined/>}/>
261
+ </Dropdown>
262
+ )
263
+ },
264
+ },
265
+ ]
266
+
267
+ // 面包屑
268
+ const crumbs = [{title: '所有桶', onClick: () => navigate('/ai/oss/bucket')}, {title: decodedBucket}]
269
+ if (currentPrefix) {
270
+ const parts = currentPrefix.split('/').filter(Boolean)
271
+ let acc = ''
272
+ parts.forEach(p => {
273
+ acc += p + '/'
274
+ crumbs.push({title: p, onClick: () => handleBreadcrumb(acc)})
275
+ })
276
+ }
277
+
278
+ return (
279
+ <div>
280
+ {/* 顶部 */}
281
+ <div style={{
282
+ display: 'flex', alignItems: 'center', gap: 12,
283
+ background: '#fff', padding: '14px 16px', borderRadius: 10,
284
+ boxShadow: '0 1px 4px rgba(0,0,0,0.04)', marginBottom: 16,
285
+ }}>
286
+ <Button type="text" icon={<ArrowLeftOutlined/>} onClick={() => navigate('/ai/oss/bucket')}>
287
+ 返回
288
+ </Button>
289
+ <FolderOutlined style={{fontSize: 18, color: '#faad14'}}/>
290
+ <span style={{fontSize: 16, fontWeight: 600}}>{decodedBucket}</span>
291
+ <div style={{flex: 1}}/>
292
+ <Input.Search
293
+ placeholder="搜索对象名..."
294
+ value={searchKey}
295
+ onChange={e => setSearchKey(e.target.value)}
296
+ style={{width: 220}}
297
+ allowClear
298
+ />
299
+ <Button icon={<ReloadOutlined/>} onClick={() => {
300
+ loadObjects(currentPrefix);
301
+ loadStats()
302
+ }}/>
303
+ </div>
304
+
305
+ {/* 统计 */}
306
+ <Row gutter={16} style={{marginBottom: 16}}>
307
+ <Col span={8}><Card><Statistic title="对象数" value={stats.objectCount || 0}/></Card></Col>
308
+ <Col span={8}><Card><Statistic title="总大小" value={fmtSize(stats.totalSize)}/></Card></Col>
309
+ <Col span={8}>
310
+ <Card>
311
+ <Statistic
312
+ title="Provider"
313
+ value={stats.activeProvider || '-'}
314
+ valueStyle={{fontSize: 18, color: '#1890ff'}}
315
+ prefix={<CloudOutlined/>}
316
+ />
317
+ </Card>
318
+ </Col>
319
+ </Row>
320
+
321
+ {/* 工具栏 */}
322
+ <div style={{
323
+ display: 'flex', alignItems: 'center', gap: 8,
324
+ background: '#fff', padding: '10px 16px', borderRadius: 10,
325
+ boxShadow: '0 1px 4px rgba(0,0,0,0.04)', marginBottom: 12,
326
+ }}>
327
+ <Breadcrumb items={crumbs} style={{flex: 1}}/>
328
+ {selectedRowKeys.length > 0 && (
329
+ <Popconfirm
330
+ title={`确定删除 ${selectedRowKeys.length} 个对象?`}
331
+ onConfirm={() => handleDelete(selectedRowKeys)}
332
+ okText="确认"
333
+ cancelText="取消"
334
+ okType="danger"
335
+ >
336
+ <Button danger size="small" icon={<DeleteOutlined/>}>
337
+ 批量删除 ({selectedRowKeys.length})
338
+ </Button>
339
+ </Popconfirm>
340
+ )}
341
+ <Button size="small" icon={<FolderAddOutlined/>} onClick={() => setFolderModalOpen(true)}>
342
+ 新建文件夹
343
+ </Button>
344
+ <Upload beforeUpload={handleUpload} showUploadList={false} disabled={uploadLoading}>
345
+ <Button size="small" type="primary" icon={<UploadOutlined/>} loading={uploadLoading}>
346
+ 上传文件
347
+ </Button>
348
+ </Upload>
349
+ </div>
350
+
351
+ {/* 对象列表 */}
352
+ <Card styles={{body: {padding: 0}}}>
353
+ {loading ? (
354
+ <div style={{textAlign: 'center', padding: 60}}><Spin size="large"/></div>
355
+ ) : filtered.length === 0 ? (
356
+ <Empty
357
+ image={<CloudOutlined style={{fontSize: 48, color: '#bfbfbf'}}/>}
358
+ description={searchKey ? '没有匹配的对象' : '桶是空的,上传第一个文件吧'}
359
+ style={{padding: 60}}
360
+ />
361
+ ) : (
362
+ <Table
363
+ rowKey="key"
364
+ columns={columns}
365
+ dataSource={filtered}
366
+ pagination={false}
367
+ size="middle"
368
+ rowSelection={{
369
+ selectedRowKeys,
370
+ onChange: setSelectedRowKeys,
371
+ }}
372
+ onRow={(record) => ({
373
+ onDoubleClick: () => {
374
+ if (record.key?.endsWith('/') || record.folder) handleEnterFolder(record.key)
375
+ },
376
+ })}
377
+ />
378
+ )}
379
+ </Card>
380
+
381
+ {/* 新建文件夹 */}
382
+ <Modal
383
+ title="新建文件夹"
384
+ open={folderModalOpen}
385
+ onCancel={() => {
386
+ setFolderModalOpen(false);
387
+ folderForm.resetFields()
388
+ }}
389
+ onOk={handleCreateFolder}
390
+ okText="创建"
391
+ cancelText="取消"
392
+ destroyOnClose
393
+ >
394
+ <Form form={folderForm} layout="vertical" style={{marginTop: 16}}>
395
+ <Form.Item
396
+ name="name"
397
+ label="文件夹名"
398
+ rules={[{required: true, message: '请输入文件夹名'}]}
399
+ >
400
+ <Input prefix={<FolderOutlined/>} placeholder="my-folder"/>
401
+ </Form.Item>
402
+ <div style={{fontSize: 12, color: '#999'}}>
403
+ 将创建在:<code>{decodedBucket}/{currentPrefix || ''}</code>
404
+ </div>
405
+ </Form>
406
+ </Modal>
407
+ </div>
408
+ )
409
+ }