feat: v0.12.4 素材库优化 + UI 修复
①素材组名字自动从火山同步(打开素材库时一次 API 调用) ②空素材组显示「暂无图片」替代烂图(列表页 + @搜索弹窗) ③@搜索支持英文角色名(去掉中文正则限制) ④素材上传页显示图片尺寸要求红字提示 ⑤图片尺寸报错改为白话文案 ⑥个人中心页面支持滚动 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6d4142fff0
commit
0a1a3a266c
@ -2750,18 +2750,35 @@ def asset_groups_view(request):
|
|||||||
team = request.user.team
|
team = request.user.team
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
groups = (
|
groups = list(
|
||||||
AssetGroup.objects
|
AssetGroup.objects
|
||||||
.filter(team=team)
|
.filter(team=team)
|
||||||
.annotate(asset_count=Count('assets'))
|
.annotate(asset_count=Count('assets'))
|
||||||
.order_by('-created_at')
|
.order_by('-created_at')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 从火山同步素材组名字(一次 API 调用)
|
||||||
|
remote_ids = [g.remote_group_id for g in groups if g.remote_group_id]
|
||||||
|
if remote_ids:
|
||||||
|
try:
|
||||||
|
from utils.assets_client import list_asset_groups
|
||||||
|
remote_items, _ = list_asset_groups(page=1, page_size=100)
|
||||||
|
remote_name_map = {item['Id']: item.get('Name', '') for item in remote_items if 'Id' in item}
|
||||||
|
for g in groups:
|
||||||
|
if g.remote_group_id and g.remote_group_id in remote_name_map:
|
||||||
|
remote_name = remote_name_map[g.remote_group_id]
|
||||||
|
if remote_name and remote_name != g.name:
|
||||||
|
g.name = remote_name
|
||||||
|
g.save(update_fields=['name'])
|
||||||
|
except Exception:
|
||||||
|
pass # 同步失败不影响列表展示
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for g in groups:
|
for g in groups:
|
||||||
results.append({
|
results.append({
|
||||||
'id': g.id,
|
'id': g.id,
|
||||||
'name': g.name,
|
'name': g.name,
|
||||||
'thumbnail_url': g.thumbnail_url,
|
'thumbnail_url': g.thumbnail_url if g.asset_count > 0 else '',
|
||||||
'asset_count': g.asset_count,
|
'asset_count': g.asset_count,
|
||||||
'remote_group_id': g.remote_group_id,
|
'remote_group_id': g.remote_group_id,
|
||||||
'created_at': g.created_at.isoformat(),
|
'created_at': g.created_at.isoformat(),
|
||||||
@ -2784,12 +2801,12 @@ def asset_groups_view(request):
|
|||||||
w, h = img.size
|
w, h = img.size
|
||||||
if w < 300 or h < 300:
|
if w < 300 or h < 300:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': f'图片尺寸太小({w}x{h}),宽高均需 ≥ 300px'},
|
{'error': f'图片太小了,请上传更大的图片(当前 {w}x{h},最小要求 300x300)'},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
if w > 6000 or h > 6000:
|
if w > 6000 or h > 6000:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': f'图片尺寸太大({w}x{h}),宽高均需 ≤ 6000px'},
|
{'error': f'图片太大了,请压缩后重试(当前 {w}x{h},最大支持 6000x6000)'},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
file.seek(0) # Reset after PIL read
|
file.seek(0) # Reset after PIL read
|
||||||
@ -2868,6 +2885,18 @@ def asset_group_detail_view(request, group_id):
|
|||||||
return Response({'error': '素材组不存在'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'error': '素材组不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
# 同步火山端的素材组名字
|
||||||
|
if group.remote_group_id:
|
||||||
|
try:
|
||||||
|
from utils.assets_client import get_asset_group
|
||||||
|
remote = get_asset_group(group.remote_group_id)
|
||||||
|
remote_name = remote.get('Name', '')
|
||||||
|
if remote_name and remote_name != group.name:
|
||||||
|
group.name = remote_name
|
||||||
|
group.save(update_fields=['name'])
|
||||||
|
except Exception:
|
||||||
|
pass # 查不到就用本地名字
|
||||||
|
|
||||||
assets_qs = Asset.objects.filter(group=group).order_by('-created_at')
|
assets_qs = Asset.objects.filter(group=group).order_by('-created_at')
|
||||||
asset_list = []
|
asset_list = []
|
||||||
for a in assets_qs:
|
for a in assets_qs:
|
||||||
@ -2947,12 +2976,12 @@ def asset_group_add_asset_view(request, group_id):
|
|||||||
w, h = img.size
|
w, h = img.size
|
||||||
if w < 300 or h < 300:
|
if w < 300 or h < 300:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': f'图片尺寸太小({w}x{h}),宽高均需 ≥ 300px'},
|
{'error': f'图片太小了,请上传更大的图片(当前 {w}x{h},最小要求 300x300)'},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
if w > 6000 or h > 6000:
|
if w > 6000 or h > 6000:
|
||||||
return Response(
|
return Response(
|
||||||
{'error': f'图片尺寸太大({w}x{h}),宽高均需 ≤ 6000px'},
|
{'error': f'图片太大了,请压缩后重试(当前 {w}x{h},最大支持 6000x6000)'},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
file.seek(0)
|
file.seek(0)
|
||||||
@ -3053,6 +3082,7 @@ def asset_search_view(request):
|
|||||||
groups = (
|
groups = (
|
||||||
AssetGroup.objects
|
AssetGroup.objects
|
||||||
.filter(team=team, name__icontains=q)
|
.filter(team=team, name__icontains=q)
|
||||||
|
.annotate(asset_count=Count('assets'))
|
||||||
.order_by('-created_at')[:20]
|
.order_by('-created_at')[:20]
|
||||||
)
|
)
|
||||||
results = []
|
results = []
|
||||||
@ -3060,7 +3090,8 @@ def asset_search_view(request):
|
|||||||
results.append({
|
results.append({
|
||||||
'id': g.id,
|
'id': g.id,
|
||||||
'name': g.name,
|
'name': g.name,
|
||||||
'thumbnail_url': g.thumbnail_url,
|
'thumbnail_url': g.thumbnail_url if g.asset_count > 0 else '',
|
||||||
|
'asset_count': g.asset_count,
|
||||||
'remote_group_id': g.remote_group_id,
|
'remote_group_id': g.remote_group_id,
|
||||||
})
|
})
|
||||||
return Response({'results': results})
|
return Response({'results': results})
|
||||||
|
|||||||
@ -167,6 +167,12 @@ def get_asset(asset_id: str) -> dict:
|
|||||||
return _do_request('GetAsset', body)
|
return _do_request('GetAsset', body)
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_group(group_id: str) -> dict:
|
||||||
|
"""Get single asset group details."""
|
||||||
|
body = {'Id': group_id, 'ProjectName': PROJECT_NAME}
|
||||||
|
return _do_request('GetAssetGroup', body)
|
||||||
|
|
||||||
|
|
||||||
def update_asset_group(group_id: str, name: str = None, description: str = None):
|
def update_asset_group(group_id: str, name: str = None, description: str = None):
|
||||||
"""Update an asset group's name and/or description."""
|
"""Update an asset group's name and/or description."""
|
||||||
body = {'Id': group_id, 'ProjectName': PROJECT_NAME}
|
body = {'Id': group_id, 'ProjectName': PROJECT_NAME}
|
||||||
|
|||||||
@ -211,7 +211,11 @@ export function AssetLibraryModal({ open, onClose }: Props) {
|
|||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<div key={group.id} className={styles.card} onClick={() => handleGroupClick(group)}>
|
<div key={group.id} className={styles.card} onClick={() => handleGroupClick(group)}>
|
||||||
|
{group.asset_count === 0 ? (
|
||||||
|
<div className={styles.cardThumb} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--color-text-disabled)', fontSize: 12 }}>暂无图片</div>
|
||||||
|
) : (
|
||||||
<img src={tosThumb(group.thumbnail_url, 300)} alt={group.name} className={styles.cardThumb} />
|
<img src={tosThumb(group.thumbnail_url, 300)} alt={group.name} className={styles.cardThumb} />
|
||||||
|
)}
|
||||||
<div className={styles.cardInfo}>
|
<div className={styles.cardInfo}>
|
||||||
{editingName && editingName.id === group.id ? (
|
{editingName && editingName.id === group.id ? (
|
||||||
<div className={styles.inlineEditWrap} onClick={(e) => e.stopPropagation()}>
|
<div className={styles.inlineEditWrap} onClick={(e) => e.stopPropagation()}>
|
||||||
@ -406,6 +410,7 @@ export function AssetLibraryModal({ open, onClose }: Props) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className={styles.dropZoneWarning}>⚠️ 素材上传后无法删除,请确认后再上传</div>
|
<div className={styles.dropZoneWarning}>⚠️ 素材上传后无法删除,请确认后再上传</div>
|
||||||
|
<div className={styles.dropZoneWarning}>⚠️ 图片尺寸要求:宽高均需在 300~6000 像素之间</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
|
|||||||
@ -125,6 +125,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: #2a2a3a;
|
background: #2a2a3a;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbMedia {
|
.thumbMedia {
|
||||||
|
|||||||
@ -271,8 +271,8 @@ export function PromptInput() {
|
|||||||
typedAtRef.current = true;
|
typedAtRef.current = true;
|
||||||
setMentionMode('references');
|
setMentionMode('references');
|
||||||
openMentionPopup();
|
openMentionPopup();
|
||||||
} else if (/[\u4e00-\u9fff]+/.test(textAfterAt) && !textAfterAt.includes(' ')) {
|
} else if (textAfterAt.length > 0 && !textAfterAt.includes(' ')) {
|
||||||
// Chinese text after @, search assets
|
// Text after @, search assets (Chinese + English)
|
||||||
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
|
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
|
||||||
searchTimerRef.current = setTimeout(() => {
|
searchTimerRef.current = setTimeout(() => {
|
||||||
assetsApi.search(textAfterAt).then((res) => {
|
assetsApi.search(textAfterAt).then((res) => {
|
||||||
@ -641,7 +641,11 @@ export function PromptInput() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.mentionThumb}>
|
<div className={styles.mentionThumb}>
|
||||||
|
{group.thumbnail_url ? (
|
||||||
<img src={tosThumb(group.thumbnail_url, 72)} alt="" className={styles.thumbMedia} />
|
<img src={tosThumb(group.thumbnail_url, 72)} alt="" className={styles.thumbMedia} />
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: 9, color: 'var(--color-text-disabled)' }}>无图</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.mentionLabel}>{group.name}</span>
|
<span className={styles.mentionLabel}>{group.name}</span>
|
||||||
<span className={styles.mentionType}>人像</span>
|
<span className={styles.mentionType}>人像</span>
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 24px 20px 60px;
|
padding: 24px 20px 60px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user