feat: 添加私域虚拟人素材功能
- init.py: 新增6个虚拟人函数(create/list/upload/sync等),注册到ServerEnv - api_mapping: 新增create_group→CreateAssetGroup映射 - 外部API: 5个rl_virtual_*.dspy端点(创建组合/列表/上传/素材列表/状态) - 前端API: 4个submit/get dspy端点(UI表单提交和数据获取) - UI页面: 3个页面(创建组合/上传素材/查看素材) - index.ui: 左侧导航新增虚拟人素材分区(3个按钮) - load_path.py: RBAC新增virtual页面和api/%路径 - docs: api_downapp.md新增虚拟人API文档(5个端点)
This commit is contained in:
parent
fa8b35072d
commit
925f58b025
@ -302,6 +302,195 @@ curl -X POST 'https://token.opencomputing.cn/reallife_asset/api/rl_assets.dspy'
|
||||
|
||||
---
|
||||
|
||||
## 私域虚拟人素材 API
|
||||
|
||||
> 虚拟人素材(AIGC)与真人认证素材使用独立的接口。虚拟人素材无需真人认证流程,直接创建素材组合后上传即可。
|
||||
|
||||
### 业务流程
|
||||
|
||||
1. **创建素材组合**:调用 `rl_virtual_create_group.dspy` 创建 AIGC 类型的素材组合。
|
||||
2. **查询素材组合**:调用 `rl_virtual_groups.dspy` 获取当前机构下所有虚拟人素材组合。
|
||||
3. **上传素材**:调用 `rl_virtual_upload.dspy` 上传虚拟人素材到指定组合。
|
||||
4. **查询素材列表**:调用 `rl_virtual_assets.dspy` 获取组合下的素材。
|
||||
5. **状态同步**:调用 `rl_virtual_status.dspy` 查询素材处理状态。
|
||||
|
||||
---
|
||||
|
||||
## 5. 创建虚拟人素材组合
|
||||
**Endpoint**: `/reallife_asset/api/rl_virtual_create_group.dspy`
|
||||
|
||||
### 请求参数
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `vendor` | 是 | 供应商标识,如 `volcengine` |
|
||||
| `name` | 是 | 素材组合名称 |
|
||||
| `description` | 否 | 组合描述 |
|
||||
| `project_name` | 否 | 项目名称,默认 `default` |
|
||||
|
||||
### 请求示例
|
||||
```http
|
||||
POST /reallife_asset/api/rl_virtual_create_group.dspy
|
||||
Authorization: Bearer ***
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"vendor": "volcengine",
|
||||
"name": "虚拟角色A",
|
||||
"description": "用于Seedance 2.0视频生成"
|
||||
}
|
||||
```
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"id": "local-id-xxx",
|
||||
"vendor_group_id": "volc-group-xxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### curl 示例
|
||||
```bash
|
||||
curl -X POST 'https://token.opencomputing.cn/reallife_asset/api/rl_virtual_create_group.dspy' \
|
||||
-H 'Authorization: Bearer *** \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"vendor":"volcengine","name":"虚拟角色A","description":"测试组合"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 查询虚拟人素材组合列表
|
||||
**Endpoint**: `/reallife_asset/api/rl_virtual_groups.dspy`
|
||||
|
||||
自动从 Bearer Token 获取 `org_id`,返回当前机构下所有虚拟人素材组合。
|
||||
|
||||
### 请求示例
|
||||
```http
|
||||
POST /reallife_asset/api/rl_virtual_groups.dspy
|
||||
Authorization: Bearer ***
|
||||
```
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"groups": [
|
||||
{
|
||||
"vendor_group_id": "volc-group-xxx",
|
||||
"vendor": "volcengine",
|
||||
"name": "虚拟角色A",
|
||||
"status": "active",
|
||||
"create_time": "2026-06-02 12:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 上传虚拟人素材
|
||||
**Endpoint**: `/reallife_asset/api/rl_virtual_upload.dspy`
|
||||
|
||||
### 请求参数
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `vendor_group_id` | 是 | 素材组合 ID |
|
||||
| `source_url` | 是 | 素材 URL 或 base64 data URI |
|
||||
| `asset_type` | 否 | Image/Video/Audio,自动检测 |
|
||||
| `name` | 否 | 素材名称 |
|
||||
|
||||
### 请求示例
|
||||
```http
|
||||
POST /reallife_asset/api/rl_virtual_upload.dspy
|
||||
Authorization: Bearer ***
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"vendor_group_id": "volc-group-xxx",
|
||||
"source_url": "https://example.com/avatar.jpg",
|
||||
"asset_type": "Image",
|
||||
"name": "虚拟人正面照"
|
||||
}
|
||||
```
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"id": "asset-local-xxx",
|
||||
"vendor_asset_id": "volc-asset-xxx",
|
||||
"status": "Processing"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### curl 示例
|
||||
```bash
|
||||
curl -X POST 'https://token.opencomputing.cn/reallife_asset/api/rl_virtual_upload.dspy' \
|
||||
-H 'Authorization: Bearer *** \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"vendor_group_id":"volc-group-xxx","source_url":"https://example.com/avatar.jpg","asset_type":"Image","name":"虚拟人正面照"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 查询虚拟人素材列表
|
||||
**Endpoint**: `/reallife_asset/api/rl_virtual_assets.dspy`
|
||||
|
||||
### 请求参数
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `vendor_group_id` | 是 | 素材组合 ID |
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"assets": [
|
||||
{
|
||||
"id": "asset-local-xxx",
|
||||
"vendor_asset_id": "volc-asset-xxx",
|
||||
"name": "虚拟人正面照",
|
||||
"asset_type": "Image",
|
||||
"status": "Active",
|
||||
"url": "https://... (临时下载链接)",
|
||||
"create_time": "2026-06-02 12:30:00"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 查询虚拟人素材处理状态
|
||||
**Endpoint**: `/reallife_asset/api/rl_virtual_status.dspy`
|
||||
|
||||
### 请求参数
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `asset_id` | 是 | 素材 ID |
|
||||
|
||||
### 返回示例
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"status": "Active",
|
||||
"url": "https://... (临时下载链接)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误代码说明
|
||||
|
||||
| 错误信息 | 原因 | 解决方案 |
|
||||
@ -312,3 +501,5 @@ curl -X POST 'https://token.opencomputing.cn/reallife_asset/api/rl_assets.dspy'
|
||||
| `素材不存在或无权访问` | `asset_id` 无效或归属错误 | 检查 ID 是否正确 |
|
||||
| `未找到对应的认证会话` | `BytedToken` 无效 | 检查回调参数 |
|
||||
| `尚未完成认证或认证失败` | 认证未完成 | 等待用户完成 H5 认证 |
|
||||
| `vendor和name为必填参数` | 创建虚拟人组合缺少参数 | 补充 vendor 和 name |
|
||||
| `创建素材组合失败,未返回组合ID` | 供应商 API 未返回有效 ID | 检查供应商配置和网络 |
|
||||
|
||||
@ -745,6 +745,365 @@ async def rl_query_groups(org_id):
|
||||
return {"success": True, "groups": groups}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Virtual Human (私域虚拟人) Asset Management — GroupType: AIGC
|
||||
# ============================================================
|
||||
|
||||
async def rl_create_virtual_group(org_id, vendor, name, description="",
|
||||
project_name="default", user_id=None):
|
||||
"""Create AIGC virtual human asset group via CreateAssetGroup API."""
|
||||
params = {
|
||||
"Name": name,
|
||||
"Description": description,
|
||||
"GroupType": "AIGC",
|
||||
"ProjectName": project_name,
|
||||
}
|
||||
result = await _call_vendor(vendor, "create_group", params)
|
||||
|
||||
if "error" in result or "Error" in result:
|
||||
return {"success": False, "message": result.get("error", result.get("Message", "API调用失败"))}
|
||||
|
||||
vendor_group_id = result.get("Id", result.get("Result", {}).get("Id", ""))
|
||||
if not vendor_group_id:
|
||||
return {"success": False, "message": "创建素材组合失败,未返回组合ID"}
|
||||
|
||||
# Save to rl_asset_group
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
gid = getID()
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
await sor.C("rl_asset_group", {
|
||||
"id": gid,
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"name": name,
|
||||
"title": name,
|
||||
"description": description,
|
||||
"group_type": "AIGC",
|
||||
"project_name": project_name,
|
||||
"status": "active",
|
||||
"vendor_group_id": vendor_group_id,
|
||||
"byted_token": "",
|
||||
"h5_link": "",
|
||||
"callback_url": "",
|
||||
"created_by": user_id or "",
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
|
||||
# Register in rl_org_group for org isolation
|
||||
mapping_id = getID()
|
||||
existing = await sor.R("rl_org_group", {
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vendor_group_id,
|
||||
})
|
||||
if not existing:
|
||||
await sor.C("rl_org_group", {
|
||||
"id": mapping_id,
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vendor_group_id,
|
||||
"local_group_id": gid,
|
||||
"name": name,
|
||||
"status": "active",
|
||||
"create_time": now,
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"id": gid,
|
||||
"vendor_group_id": vendor_group_id,
|
||||
}
|
||||
|
||||
|
||||
async def rl_list_virtual_groups(org_id):
|
||||
"""List virtual human (AIGC) groups for an org."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_org_group", {"org_id": org_id})
|
||||
if not recs:
|
||||
return {"success": True, "groups": []}
|
||||
|
||||
groups = []
|
||||
for r in recs:
|
||||
local_group_id = r.local_group_id
|
||||
# Check group_type from rl_asset_group
|
||||
grp_recs = await sor.R("rl_asset_group", {"id": local_group_id})
|
||||
if grp_recs and getattr(grp_recs[0], "group_type", "") == "AIGC":
|
||||
groups.append({
|
||||
"vendor_group_id": r.vendor_group_id,
|
||||
"vendor": r.vendor,
|
||||
"name": getattr(r, "name", ""),
|
||||
"status": r.status,
|
||||
"create_time": getattr(r, "create_time", ""),
|
||||
})
|
||||
|
||||
return {"success": True, "groups": groups}
|
||||
|
||||
|
||||
async def rl_sync_virtual_groups_from_vendor(org_id, vendor, project_name="default"):
|
||||
"""Sync AIGC asset groups from vendor to local DB."""
|
||||
params = {
|
||||
"Filter": {"GroupType": "AIGC"},
|
||||
"PageNumber": 1,
|
||||
"PageSize": 100,
|
||||
}
|
||||
result = await _call_vendor(vendor, "list_groups", params)
|
||||
|
||||
items = result.get("Items", result.get("Result", {}).get("Items", []))
|
||||
synced = 0
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
for item in items:
|
||||
vgid = item.get("Id", "")
|
||||
if not vgid:
|
||||
continue
|
||||
existing = await sor.R("rl_asset_group", {
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vgid,
|
||||
})
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
if existing:
|
||||
await sor.U("rl_asset_group", {
|
||||
"id": existing[0].id,
|
||||
"name": item.get("Name", ""),
|
||||
"title": item.get("Title", item.get("Name", "")),
|
||||
"description": item.get("Description", ""),
|
||||
"update_time": now,
|
||||
})
|
||||
local_gid = existing[0].id
|
||||
else:
|
||||
gid = getID()
|
||||
await sor.C("rl_asset_group", {
|
||||
"id": gid,
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vgid,
|
||||
"name": item.get("Name", ""),
|
||||
"title": item.get("Title", item.get("Name", "")),
|
||||
"description": item.get("Description", ""),
|
||||
"group_type": "AIGC",
|
||||
"project_name": item.get("ProjectName", project_name),
|
||||
"status": "active",
|
||||
"create_time": item.get("CreateTime", now),
|
||||
"update_time": now,
|
||||
})
|
||||
local_gid = gid
|
||||
# Ensure rl_org_group mapping exists
|
||||
og_existing = await sor.R("rl_org_group", {
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vgid,
|
||||
})
|
||||
if not og_existing:
|
||||
await sor.C("rl_org_group", {
|
||||
"id": getID(),
|
||||
"org_id": org_id,
|
||||
"vendor": vendor,
|
||||
"vendor_group_id": vgid,
|
||||
"local_group_id": local_gid,
|
||||
"name": item.get("Name", ""),
|
||||
"status": "active",
|
||||
"create_time": now,
|
||||
})
|
||||
synced += 1
|
||||
|
||||
return {"success": True, "synced": synced}
|
||||
|
||||
|
||||
async def rl_sync_virtual_assets_from_vendor(org_id, local_group_id):
|
||||
"""Sync assets for a virtual group from vendor to local DB."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_asset_group", {"id": local_group_id})
|
||||
if not recs:
|
||||
return {"success": False, "message": "素材组合不存在"}
|
||||
grp = recs[0]
|
||||
vendor = grp.vendor
|
||||
vendor_group_id = grp.vendor_group_id
|
||||
project_name = grp.project_name or "default"
|
||||
|
||||
if not vendor_group_id:
|
||||
return {"success": False, "message": "无供应商端组合ID"}
|
||||
|
||||
params = {
|
||||
"Filter": {"GroupType": "AIGC", "GroupIds": [vendor_group_id]},
|
||||
"PageNumber": 1,
|
||||
"PageSize": 100,
|
||||
}
|
||||
result = await _call_vendor(vendor, "list_assets", params)
|
||||
|
||||
items = result.get("Items", result.get("Result", {}).get("Items", []))
|
||||
synced = 0
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
for item in items:
|
||||
vaid = item.get("Id", "")
|
||||
if not vaid:
|
||||
continue
|
||||
existing = await sor.R("rl_asset", {
|
||||
"vendor": vendor,
|
||||
"vendor_asset_id": vaid,
|
||||
})
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
if existing:
|
||||
await sor.U("rl_asset", {
|
||||
"id": existing[0].id,
|
||||
"status": item.get("Status", ""),
|
||||
"url": item.get("URL", ""),
|
||||
"name": item.get("Name", existing[0].name),
|
||||
"update_time": now,
|
||||
})
|
||||
else:
|
||||
aid = getID()
|
||||
await sor.C("rl_asset", {
|
||||
"id": aid,
|
||||
"org_id": org_id,
|
||||
"group_id": local_group_id,
|
||||
"vendor": vendor,
|
||||
"vendor_asset_id": vaid,
|
||||
"asset_type": item.get("AssetType", "Image"),
|
||||
"name": item.get("Name", ""),
|
||||
"status": item.get("Status", "Processing"),
|
||||
"url": item.get("URL", ""),
|
||||
"asset_uri": f"asset://{vaid}",
|
||||
"project_name": item.get("ProjectName", project_name),
|
||||
"create_time": item.get("CreateTime", now),
|
||||
"update_time": now,
|
||||
})
|
||||
synced += 1
|
||||
|
||||
return {"success": True, "synced": synced}
|
||||
|
||||
|
||||
async def rl_list_virtual_assets_client(org_id, vendor_group_id):
|
||||
"""Client API: List assets for a virtual group, sync from vendor first."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
|
||||
# Validate org owns this group
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
grp_recs = await sor.R("rl_org_group", {
|
||||
"org_id": org_id,
|
||||
"vendor_group_id": vendor_group_id,
|
||||
})
|
||||
if not grp_recs:
|
||||
return {"success": False, "message": "无效的素材组合或无权访问"}
|
||||
vendor = grp_recs[0].vendor
|
||||
local_group_id = grp_recs[0].local_group_id
|
||||
|
||||
# Verify it's an AIGC group
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
grp_check = await sor.R("rl_asset_group", {"id": local_group_id})
|
||||
if grp_check and getattr(grp_check[0], "group_type", "") != "AIGC":
|
||||
return {"success": False, "message": "该组合不是虚拟人素材组合"}
|
||||
|
||||
# Sync from vendor
|
||||
try:
|
||||
await rl_sync_virtual_assets_from_vendor(org_id, local_group_id)
|
||||
except Exception as e:
|
||||
debug(f"rl_list_virtual_assets_client: vendor sync failed: {e}")
|
||||
|
||||
# Query local
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_asset", {
|
||||
"org_id": org_id,
|
||||
"group_id": local_group_id,
|
||||
})
|
||||
if not recs:
|
||||
return {"success": True, "assets": [], "total": 0}
|
||||
|
||||
assets = []
|
||||
for r in recs:
|
||||
assets.append({
|
||||
"id": r.id,
|
||||
"name": getattr(r, "name", ""),
|
||||
"status": getattr(r, "status", ""),
|
||||
"url": getattr(r, "url", ""),
|
||||
"asset_type": getattr(r, "asset_type", ""),
|
||||
"vendor_asset_id": getattr(r, "vendor_asset_id", ""),
|
||||
"create_time": getattr(r, "create_time", ""),
|
||||
})
|
||||
|
||||
return {"success": True, "assets": assets, "total": len(assets)}
|
||||
|
||||
|
||||
async def rl_upload_virtual_asset(org_id, vendor_group_id, source_url,
|
||||
asset_type, name, user_id):
|
||||
"""Upload asset to a virtual (AIGC) group with org validation."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
|
||||
# Validate org owns this group
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_org_group", {
|
||||
"org_id": org_id,
|
||||
"vendor_group_id": vendor_group_id,
|
||||
})
|
||||
if not recs:
|
||||
return {"success": False, "message": "无效的素材组合ID或无权访问"}
|
||||
local_group_id = recs[0].local_group_id
|
||||
vendor = recs[0].vendor
|
||||
|
||||
# Get project_name from group
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
grp_recs = await sor.R("rl_asset_group", {"id": local_group_id})
|
||||
project_name = grp_recs[0].project_name if grp_recs else "default"
|
||||
|
||||
# Call vendor API
|
||||
params = {
|
||||
"GroupId": vendor_group_id,
|
||||
"URL": source_url,
|
||||
"AssetType": asset_type,
|
||||
"ProjectName": project_name or "default",
|
||||
}
|
||||
if name:
|
||||
params["Name"] = name
|
||||
result = await _call_vendor(vendor, "upload_asset", params)
|
||||
|
||||
vendor_asset_id = result.get("Id", result.get("Result", {}).get("Id", ""))
|
||||
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
asset_id = getID()
|
||||
asset_uri = f"asset://{vendor_asset_id}" if vendor_asset_id else ""
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
await sor.C("rl_asset", {
|
||||
"id": asset_id,
|
||||
"org_id": org_id,
|
||||
"group_id": local_group_id,
|
||||
"vendor": vendor,
|
||||
"vendor_asset_id": vendor_asset_id,
|
||||
"asset_type": asset_type,
|
||||
"name": name or source_url.split("/")[-1][:50],
|
||||
"status": "Processing",
|
||||
"source_url": source_url,
|
||||
"asset_uri": asset_uri,
|
||||
"project_name": project_name or "default",
|
||||
"vendor_response": json.dumps(result, ensure_ascii=False),
|
||||
"created_by": user_id or "",
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
|
||||
has_error = "error" in result or "Error" in result
|
||||
return {
|
||||
"success": not has_error,
|
||||
"id": asset_id,
|
||||
"vendor_asset_id": vendor_asset_id,
|
||||
"status": "Processing",
|
||||
"message": result.get("error", result.get("Message", "")),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Module loader
|
||||
# ============================================================
|
||||
@ -769,4 +1128,12 @@ def load_reallife_asset():
|
||||
g.rl_handle_callback = rl_handle_callback
|
||||
g.rl_query_groups = rl_query_groups
|
||||
|
||||
# Virtual human (私域虚拟人) AIGC APIs
|
||||
g.rl_create_virtual_group = rl_create_virtual_group
|
||||
g.rl_list_virtual_groups = rl_list_virtual_groups
|
||||
g.rl_sync_virtual_groups_from_vendor = rl_sync_virtual_groups_from_vendor
|
||||
g.rl_sync_virtual_assets_from_vendor = rl_sync_virtual_assets_from_vendor
|
||||
g.rl_list_virtual_assets_client = rl_list_virtual_assets_client
|
||||
g.rl_upload_virtual_asset = rl_upload_virtual_asset
|
||||
|
||||
return True
|
||||
|
||||
@ -58,6 +58,11 @@ PATHS_LOGINED = [
|
||||
f"/{MOD}/upload_asset.ui",
|
||||
f"/{MOD}/view_assets.ui",
|
||||
|
||||
# 虚拟人素材页面
|
||||
f"/{MOD}/virtual_create_group.ui",
|
||||
f"/{MOD}/virtual_upload_asset.ui",
|
||||
f"/{MOD}/virtual_view_assets.ui",
|
||||
|
||||
# API — 所有 api/ 下的 .dspy(脚本内部通过 get_user() 做权限校验)
|
||||
f"/{MOD}/api/%",
|
||||
]
|
||||
|
||||
@ -11,7 +11,7 @@ INSERT INTO rl_vendor_config (id, vendor, vendor_title, upappid, api_mapping, st
|
||||
'volcengine',
|
||||
'火山引擎',
|
||||
'',
|
||||
'{"create_session":"CreateVisualValidateSession","check_session":"GetVisualValidateResult","upload_asset":"CreateAsset","get_asset":"GetAsset","delete_asset":"DeleteAsset","delete_group":"DeleteAssetGroup","list_groups":"ListAssetGroups","list_assets":"ListAssets","get_group":"GetAssetGroup","update_asset":"UpdateAsset","update_group":"UpdateAssetGroup"}',
|
||||
'{"create_session":"CreateVisualValidateSession","check_session":"GetVisualValidateResult","create_group":"CreateAssetGroup","upload_asset":"CreateAsset","get_asset":"GetAsset","delete_asset":"DeleteAsset","delete_group":"DeleteAssetGroup","list_groups":"ListAssetGroups","list_assets":"ListAssets","get_group":"GetAssetGroup","update_asset":"UpdateAsset","update_group":"UpdateAssetGroup"}',
|
||||
'active',
|
||||
'https://token.opencomputing.cn/reallife_asset/api/rl_callback.dspy',
|
||||
'AKLTZWE5YTY1MDRhMmIyNGFlN2JkMzBjN2U0NGFkMWQ5ODM',
|
||||
|
||||
17
wwwroot/api/get_virtual_groups.dspy
Normal file
17
wwwroot/api/get_virtual_groups.dspy
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
result = await rl_list_virtual_groups(org_id)
|
||||
groups = result.get('groups', [])
|
||||
|
||||
rows = []
|
||||
for g in groups:
|
||||
vid = g.get('vendor_group_id', '')
|
||||
vendor = g.get('vendor', '')
|
||||
name = g.get('name', '')
|
||||
if vid:
|
||||
display = f"{vendor} - {name}" if name else f"{vendor} - {vid}"
|
||||
rows.append({"value": vid, "text": display})
|
||||
|
||||
debug(f"get_virtual_groups: org={org_id}, count={len(rows)}")
|
||||
return json.dumps(rows, ensure_ascii=False)
|
||||
24
wwwroot/api/rl_virtual_assets.dspy
Normal file
24
wwwroot/api/rl_virtual_assets.dspy
Normal file
@ -0,0 +1,24 @@
|
||||
# ============================================================
|
||||
# 查询指定虚拟人素材组合下的素材列表
|
||||
# 参数: vendor_group_id(必填)
|
||||
# curl 示例:
|
||||
# curl -X POST 'https://ai.atvoe.com/reallife_asset/api/rl_virtual_assets.dspy' \
|
||||
# -H 'Authorization: Bearer *** \
|
||||
# -d 'vendor_group_id=group-xxx'
|
||||
# ============================================================
|
||||
vendor_group_id = params_kw.get("vendor_group_id", "")
|
||||
if not vendor_group_id:
|
||||
return json.dumps({"status": "error", "data": {"message": "vendor_group_id为必填参数"}})
|
||||
|
||||
try:
|
||||
org_id = (await get_userorgid()) or "0"
|
||||
result = await rl_list_virtual_assets_client(org_id, vendor_group_id)
|
||||
if result.get("success"):
|
||||
return json.dumps({"status": "ok", "data": {
|
||||
"assets": result.get("assets", []),
|
||||
"total": result.get("total", 0),
|
||||
}})
|
||||
else:
|
||||
return json.dumps({"status": "error", "data": {"message": result.get("message", "查询失败")}})
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "data": {"message": str(e)}})
|
||||
29
wwwroot/api/rl_virtual_create_group.dspy
Normal file
29
wwwroot/api/rl_virtual_create_group.dspy
Normal file
@ -0,0 +1,29 @@
|
||||
# ============================================================
|
||||
# 创建私域虚拟人素材组合
|
||||
# 参数: vendor(必填), name(必填), description(选填), project_name(选填,默认default)
|
||||
# curl 示例:
|
||||
# curl -X POST 'https://ai.atvoe.com/reallife_asset/api/rl_virtual_create_group.dspy' \
|
||||
# -H 'Authorization: Bearer *** \
|
||||
# -d 'vendor=volcengine&name=虚拟角色A&description=测试组合'
|
||||
# ============================================================
|
||||
vendor = params_kw.get("vendor", "")
|
||||
name = params_kw.get("name", "")
|
||||
description = params_kw.get("description", "")
|
||||
project_name = params_kw.get("project_name", "default")
|
||||
|
||||
if not vendor or not name:
|
||||
return json.dumps({"status": "error", "data": {"message": "vendor和name为必填参数"}})
|
||||
|
||||
try:
|
||||
org_id = (await get_userorgid()) or "0"
|
||||
user_id = await get_user()
|
||||
result = await rl_create_virtual_group(org_id, vendor, name, description, project_name, user_id)
|
||||
if result.get("success"):
|
||||
return json.dumps({"status": "ok", "data": {
|
||||
"id": result.get("id"),
|
||||
"vendor_group_id": result.get("vendor_group_id"),
|
||||
}})
|
||||
else:
|
||||
return json.dumps({"status": "error", "data": {"message": result.get("message", "创建失败")}})
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "data": {"message": str(e)}})
|
||||
15
wwwroot/api/rl_virtual_groups.dspy
Normal file
15
wwwroot/api/rl_virtual_groups.dspy
Normal file
@ -0,0 +1,15 @@
|
||||
# ============================================================
|
||||
# 查询当前机构的私域虚拟人素材组合列表
|
||||
# 参数: 无(自动从Bearer token获取org_id)
|
||||
# curl 示例:
|
||||
# curl -X POST 'https://ai.atvoe.com/reallife_asset/api/rl_virtual_groups.dspy' \
|
||||
# -H 'Authorization: Bearer *** # ============================================================
|
||||
try:
|
||||
org_id = (await get_userorgid()) or "0"
|
||||
result = await rl_list_virtual_groups(org_id)
|
||||
if result.get("success"):
|
||||
return json.dumps({"status": "ok", "data": {"groups": result.get("groups", [])}})
|
||||
else:
|
||||
return json.dumps({"status": "error", "data": {"message": result.get("message", "查询失败")}})
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "data": {"message": str(e)}})
|
||||
25
wwwroot/api/rl_virtual_status.dspy
Normal file
25
wwwroot/api/rl_virtual_status.dspy
Normal file
@ -0,0 +1,25 @@
|
||||
# ============================================================
|
||||
# 查询虚拟人素材处理状态
|
||||
# 参数: asset_id(必填)
|
||||
# curl 示例:
|
||||
# curl -X POST 'https://ai.atvoe.com/reallife_asset/api/rl_virtual_status.dspy' \
|
||||
# -H 'Authorization: Bearer *** \
|
||||
# -d 'asset_id=xxx'
|
||||
# ============================================================
|
||||
asset_id = params_kw.get("asset_id", "")
|
||||
if not asset_id:
|
||||
return json.dumps({"status": "error", "data": {"message": "asset_id为必填参数"}})
|
||||
|
||||
try:
|
||||
org_id = (await get_userorgid()) or "0"
|
||||
user_id = await get_user()
|
||||
result = await rl_sync_asset_status_user(org_id, asset_id, user_id)
|
||||
if result.get("success"):
|
||||
return json.dumps({"status": "ok", "data": {
|
||||
"status": result.get("status"),
|
||||
"url": result.get("url", ""),
|
||||
}})
|
||||
else:
|
||||
return json.dumps({"status": "error", "data": {"message": result.get("message", "查询失败")}})
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "data": {"message": str(e)}})
|
||||
34
wwwroot/api/rl_virtual_upload.dspy
Normal file
34
wwwroot/api/rl_virtual_upload.dspy
Normal file
@ -0,0 +1,34 @@
|
||||
# ============================================================
|
||||
# 上传虚拟人素材到私域素材组合
|
||||
# 参数: vendor_group_id(必填), source_url(必填), asset_type(选填,默认Image), name(选填)
|
||||
# curl 示例:
|
||||
# curl -X POST 'https://ai.atvoe.com/reallife_asset/api/rl_virtual_upload.dspy' \
|
||||
# -H 'Authorization: Bearer *** \
|
||||
# -d 'vendor_group_id=group-xxx&source_url=https://example.com/image.jpg&asset_type=Image&name=虚拟人正面'
|
||||
# ============================================================
|
||||
vendor_group_id = params_kw.get("vendor_group_id", "")
|
||||
source_url = params_kw.get("source_url", "")
|
||||
asset_type = params_kw.get("asset_type", "Image")
|
||||
name = params_kw.get("name", "")
|
||||
|
||||
if not vendor_group_id or not source_url:
|
||||
return json.dumps({"status": "error", "data": {"message": "vendor_group_id和source_url为必填参数"}})
|
||||
|
||||
# Handle base64 data URL conversion
|
||||
if source_url.startswith("data:") or (not source_url.startswith("http") and len(source_url) < 8000):
|
||||
source_url = await b64media2url(request, source_url)
|
||||
|
||||
try:
|
||||
org_id = (await get_userorgid()) or "0"
|
||||
user_id = await get_user()
|
||||
result = await rl_upload_virtual_asset(org_id, vendor_group_id, source_url, asset_type, name, user_id)
|
||||
if result.get("success"):
|
||||
return json.dumps({"status": "ok", "data": {
|
||||
"id": result.get("id"),
|
||||
"vendor_asset_id": result.get("vendor_asset_id"),
|
||||
"status": result.get("status"),
|
||||
}})
|
||||
else:
|
||||
return json.dumps({"status": "error", "data": {"message": result.get("message", "上传失败")}})
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "data": {"message": str(e)}})
|
||||
38
wwwroot/api/submit_virtual_create_group.dspy
Normal file
38
wwwroot/api/submit_virtual_create_group.dspy
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
vendor = params_kw.get('vendor', '')
|
||||
name = params_kw.get('name', '')
|
||||
description = params_kw.get('description', '')
|
||||
|
||||
if not vendor or not name:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "错误", "message": "请选择供应商并输入组合名称"}
|
||||
})
|
||||
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
user_id = (await get_user()) or ''
|
||||
|
||||
result = await rl_create_virtual_group(org_id, vendor, name, description, "default", user_id)
|
||||
|
||||
if result.get('success'):
|
||||
vendor_group_id = result.get('vendor_group_id', '')
|
||||
msg = f"素材组合创建成功!\n组合ID:{vendor_group_id}\n现在可以上传虚拟人素材到此组合。"
|
||||
return json.dumps({
|
||||
"widgettype": "Message",
|
||||
"id": "virtual_group_result_popup",
|
||||
"options": {"title": "创建成功", "message": "", "anchor": "cc"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"padding": "8px", "gap": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": msg}}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
else:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "创建失败", "message": result.get('message', '未知错误')}
|
||||
})
|
||||
145
wwwroot/api/submit_virtual_list_assets.dspy
Normal file
145
wwwroot/api/submit_virtual_list_assets.dspy
Normal file
@ -0,0 +1,145 @@
|
||||
|
||||
vendor_group_id = params_kw.get('vendor_group_id', '')
|
||||
|
||||
if not vendor_group_id:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "错误", "message": "请选择素材组合"}
|
||||
})
|
||||
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
result = await rl_list_virtual_assets_client(org_id, vendor_group_id)
|
||||
|
||||
if not result.get('success'):
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "查询失败", "message": result.get('message', '未知错误')}
|
||||
})
|
||||
|
||||
assets = result.get('assets', [])
|
||||
|
||||
if not assets:
|
||||
return json.dumps({
|
||||
"widgettype": "VBox",
|
||||
"options": {"padding": "16px", "gap": "12px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {"text": "该素材组合下暂无素材,请先上传虚拟人素材。"}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
# Build asset cards
|
||||
cards = []
|
||||
for a in assets:
|
||||
status = a.get('status', '')
|
||||
name = a.get('name', '')
|
||||
url = a.get('url', '')
|
||||
atype = a.get('asset_type', '')
|
||||
create_time = str(a.get('create_time', ''))[:16]
|
||||
asset_id = a.get('id', '')
|
||||
vendor_asset_id = a.get('vendor_asset_id', '')
|
||||
|
||||
s_lower = status.lower() if status else ''
|
||||
if s_lower in ('active', 'available', 'ready'):
|
||||
status_icon = "✅"
|
||||
elif s_lower in ('processing', 'pending', 'submitted'):
|
||||
status_icon = "⏳"
|
||||
elif s_lower in ('failed', 'error'):
|
||||
status_icon = "❌"
|
||||
else:
|
||||
status_icon = "📋"
|
||||
|
||||
card_subs = [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"gap": "8px", "alignItems": "center", "marginBottom": "4px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": name or "未命名", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": f"{status_icon} {status}", "cfontsize": 0.9}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {"text": f"类型: {atype} 创建: {create_time}", "cfontsize": 0.9}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {"text": f"Asset URI: asset://{vendor_asset_id}", "cfontsize": 0.85}
|
||||
}
|
||||
]
|
||||
|
||||
# Media preview
|
||||
if url:
|
||||
if atype == 'Image':
|
||||
card_subs.append({
|
||||
"widgettype": "Image",
|
||||
"options": {"url": url, "cwidth": 15, "cheight": 10, "objectFit": "cover", "borderRadius": "4px", "marginTop": "8px"}
|
||||
})
|
||||
elif atype == 'Video':
|
||||
card_subs.append({
|
||||
"widgettype": "VideoPlayer",
|
||||
"options": {"url": url, "cwidth": 20, "cheight": 12, "marginTop": "8px"}
|
||||
})
|
||||
elif atype == 'Audio':
|
||||
card_subs.append({
|
||||
"widgettype": "AudioPlayer",
|
||||
"options": {"url": url, "marginTop": "8px"}
|
||||
})
|
||||
|
||||
# Buttons
|
||||
check_url = request.path.rsplit('/', 1)[0] + '/submit_query_status.dspy'
|
||||
btn_subs = [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "刷新状态"},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click",
|
||||
"actiontype": "script", "target": "self",
|
||||
"script": "(async function(){" \
|
||||
"var url='" + check_url + "?_webbricks_=1&asset_id=" + asset_id + "';" \
|
||||
"var r=await fetch(url);" \
|
||||
"var j=await r.json();" \
|
||||
"await bricks.show_resp_message_or_error({json:async function(){return j}});" \
|
||||
"})()"
|
||||
}]
|
||||
}
|
||||
]
|
||||
if url:
|
||||
btn_subs.append({
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "下载"},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click",
|
||||
"actiontype": "script", "target": "self",
|
||||
"script": "window.open('" + url + "', '_blank')"
|
||||
}]
|
||||
})
|
||||
card_subs.append({
|
||||
"widgettype": "HBox",
|
||||
"options": {"gap": "8px", "marginTop": "8px"},
|
||||
"subwidgets": btn_subs
|
||||
})
|
||||
|
||||
cards.append({
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "padding": "12px", "borderRadius": "8px"},
|
||||
"subwidgets": card_subs
|
||||
})
|
||||
|
||||
result_widget = {
|
||||
"widgettype": "VBox",
|
||||
"options": {"padding": "16px", "gap": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": f"共 {len(assets)} 个虚拟人素材", "fontWeight": "bold"}},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {"gap": "12px", "minWidth": "300px"},
|
||||
"subwidgets": cards
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return json.dumps(result_widget, ensure_ascii=False)
|
||||
88
wwwroot/api/submit_virtual_upload.dspy
Normal file
88
wwwroot/api/submit_virtual_upload.dspy
Normal file
@ -0,0 +1,88 @@
|
||||
|
||||
import os
|
||||
|
||||
vendor_group_id = params_kw.get('vendor_group_id', '')
|
||||
source_url = params_kw.get('source_url', '')
|
||||
asset_type = params_kw.get('asset_type', '')
|
||||
name = params_kw.get('name', '')
|
||||
|
||||
if not vendor_group_id or not source_url:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "错误", "message": "请选择素材组合并上传素材文件", "anchor": "cc"}
|
||||
})
|
||||
|
||||
# Validate media file type from path extension
|
||||
ext = os.path.splitext(source_url.split('?')[0])[1].lower() if source_url else ''
|
||||
media_map = {
|
||||
'.jpg': 'Image', '.jpeg': 'Image', '.png': 'Image', '.gif': 'Image', '.bmp': 'Image', '.webp': 'Image', '.svg': 'Image', '.tiff': 'Image', '.heic': 'Image',
|
||||
'.mp4': 'Video', '.avi': 'Video', '.mov': 'Video', '.wmv': 'Video', '.flv': 'Video', '.mkv': 'Video', '.webm': 'Video',
|
||||
'.mp3': 'Audio', '.wav': 'Audio', '.aac': 'Audio', '.flac': 'Audio', '.ogg': 'Audio', '.wma': 'Audio', '.m4a': 'Audio',
|
||||
}
|
||||
detected_type = media_map.get(ext, '')
|
||||
if ext and not detected_type:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "错误", "message": f"不支持的文件类型: {ext},请上传图片、音频或视频文件", "anchor": "cc"}
|
||||
})
|
||||
if not asset_type and detected_type:
|
||||
asset_type = detected_type
|
||||
if not asset_type:
|
||||
asset_type = 'Image'
|
||||
|
||||
# Convert base64 / local path to public URL
|
||||
if source_url.startswith('data:') or (not source_url.startswith('http') and len(source_url) < 8000):
|
||||
source_url = await b64media2url(request, source_url)
|
||||
if not source_url:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "错误", "message": "素材文件转换失败", "anchor": "cc"}
|
||||
})
|
||||
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
user_id = (await get_user()) or ''
|
||||
|
||||
result = await rl_upload_virtual_asset(org_id, vendor_group_id, source_url, asset_type, name, user_id)
|
||||
|
||||
if result.get('success'):
|
||||
asset_id = result.get('id', '')
|
||||
vendor_asset_id = result.get('vendor_asset_id', '')
|
||||
status_text = result.get('status', 'Processing')
|
||||
msg = f"虚拟人素材已提交,当前状态:{status_text}\n素材ID:{asset_id}\n供应商资产ID:{vendor_asset_id}"
|
||||
|
||||
base_path = request.path.rsplit('/', 1)[0]
|
||||
check_url = base_path + '/submit_query_status.dspy'
|
||||
|
||||
return json.dumps({
|
||||
"widgettype": "Message",
|
||||
"id": "virtual_upload_result_popup",
|
||||
"options": {"title": "上传成功", "message": "", "anchor": "cc"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"padding": "8px", "gap": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": msg}},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "查询处理状态"},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click",
|
||||
"actiontype": "script", "target": "self",
|
||||
"script": "(async function(){"
|
||||
"var url='" + check_url + "?_webbricks_=1&asset_id=" + asset_id + "';"
|
||||
"var r=await fetch(url);"
|
||||
"var j=await r.json();"
|
||||
"await bricks.show_resp_message_or_error({json:async function(){return j}});"
|
||||
"})()"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
else:
|
||||
return json.dumps({
|
||||
"widgettype": "Error",
|
||||
"options": {"title": "上传失败", "message": result.get('message', '未知错误'), "anchor": "cc"}
|
||||
})
|
||||
@ -92,6 +92,84 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endif %}
|
||||
,{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"padding": "12px 0 8px",
|
||||
"spacing": 0
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title5",
|
||||
"options": {
|
||||
"text": "虚拟人素材",
|
||||
"color": "#94a3b8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% if is_customer or is_admin %}
|
||||
,{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "📁 创建素材组合",
|
||||
"width": "100%",
|
||||
"textAlign": "left"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rl_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('virtual_create_group.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "📤 上传虚拟人素材",
|
||||
"width": "100%",
|
||||
"textAlign": "left"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rl_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('virtual_upload_asset.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "🖼️ 查看虚拟人素材",
|
||||
"width": "100%",
|
||||
"textAlign": "left"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rl_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('virtual_view_assets.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
,{
|
||||
|
||||
73
wwwroot/virtual_create_group.ui
Normal file
73
wwwroot/virtual_create_group.ui
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "创建虚拟人素材组合",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "创建素材组合后,可将虚拟人素材(图片/视频/音频)上传至该组合,用于Seedance 2.0视频生成。",
|
||||
"cfontsize": 0.9,
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"padding": "16px",
|
||||
"spacing": 12
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "virtual_group_form",
|
||||
"options": {
|
||||
"submit_url": "{{entire_url('api/submit_virtual_create_group.dspy')}}",
|
||||
"fields": [
|
||||
{
|
||||
"uitype": "code",
|
||||
"name": "vendor",
|
||||
"label": "供应商",
|
||||
"dataurl": "{{entire_url('api/get_vendor_list.dspy')}}",
|
||||
"data_field": "value",
|
||||
"text_field": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"uitype": "str",
|
||||
"name": "name",
|
||||
"label": "组合名称",
|
||||
"placeholder": "例如:虚拟角色A",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"uitype": "str",
|
||||
"name": "description",
|
||||
"label": "描述",
|
||||
"placeholder": "可选,素材组合的描述"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submited",
|
||||
"actiontype": "script",
|
||||
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
81
wwwroot/virtual_upload_asset.ui
Normal file
81
wwwroot/virtual_upload_asset.ui
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "上传虚拟人素材",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"height": "600px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"padding": "16px",
|
||||
"spacing": 12
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "virtual_upload_form",
|
||||
"options": {
|
||||
"submit_url": "{{entire_url('api/submit_virtual_upload.dspy')}}",
|
||||
"fields": [
|
||||
{
|
||||
"uitype": "code",
|
||||
"name": "vendor_group_id",
|
||||
"label": "素材组合",
|
||||
"dataurl": "{{entire_url('api/get_virtual_groups.dspy')}}",
|
||||
"data_field": "value",
|
||||
"text_field": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"uitype": "file",
|
||||
"name": "source_url",
|
||||
"label": "素材文件",
|
||||
"accept": "image/*,audio/*,video/*",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"uitype": "code",
|
||||
"name": "asset_type",
|
||||
"label": "素材类型",
|
||||
"dataurl": "{{entire_url('api/get_asset_type_list.dspy')}}",
|
||||
"data_field": "value",
|
||||
"text_field": "text"
|
||||
},
|
||||
{
|
||||
"uitype": "str",
|
||||
"name": "name",
|
||||
"label": "素材名称",
|
||||
"placeholder": "可选,方便管理的名称"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submited",
|
||||
"actiontype": "script",
|
||||
"script": "await bricks.show_resp_message_or_error(event.params)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
75
wwwroot/virtual_view_assets.ui
Normal file
75
wwwroot/virtual_view_assets.ui
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "查看虚拟人素材",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Form",
|
||||
"id": "virtual_view_form",
|
||||
"options": {
|
||||
"cheight": 8,
|
||||
"fields": [
|
||||
{
|
||||
"uitype": "code",
|
||||
"name": "vendor_group_id",
|
||||
"label": "素材组合",
|
||||
"dataurl": "{{entire_url('api/get_virtual_groups.dspy')}}",
|
||||
"data_field": "value",
|
||||
"text_field": "text",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "submit",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.rl_virtual_asset_results",
|
||||
"options": {
|
||||
"method": "POST",
|
||||
"url": "{{entire_url('api/submit_virtual_list_assets.dspy')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"padding": "0",
|
||||
"marginTop": "12px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"css": "filler"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "rl_virtual_asset_results",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user