@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,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
|
+
}
|