feat: 视觉风格修改
This commit is contained in:
@@ -38,27 +38,27 @@ export const STYLE_SET_OPTIONS = [
|
||||
{
|
||||
value: 1,
|
||||
label: '北欧极简',
|
||||
prompt: '北欧极简风:浅灰或米白背景,柔和漫射光,低饱和色调,画面留白充足,构图克制干净',
|
||||
prompt: 'Nordic editorial e-commerce style for contextual and marketing images only: warm ivory or light greige base, soft window daylight with a subtle warm practical-light accent, low-saturation natural palette, pale wood and tactile linen details, spacious orderly composition, and at most one or two restrained lifestyle props. Keep the product exact and prominent. When text is requested, reserve a clean top text-safe zone with a clear headline-first hierarchy. Never override a task-mandated pure-white background, no-text, or no-props instruction',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '清新明亮',
|
||||
prompt: '清新明亮风:明亮的白色到浅蓝渐变背景,高调光线,色彩明快通透,整体轻盈干净',
|
||||
prompt: 'Fresh, bright, family-friendly e-commerce style for contextual and marketing images only: crisp white with pale sky-blue, mint, or other accents sampled from the product palette; high-key daylight, luminous but natural color, soft clean shadows, airy mobile-first composition, and a cheerful polished retail mood. When text is requested, use a generous top text-safe zone, one bold headline plus one short supporting line or pill, and small simple accent icons only when they clarify a real feature. Never override a task-mandated pure-white background, no-text, or no-props instruction',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '高级感深色',
|
||||
prompt: '高级质感风:深灰或炭黑背景,戏剧性侧光打光,突出商品材质与光泽,沉稳高级',
|
||||
prompt: 'Premium dark editorial e-commerce style for contextual and marketing images only: deep charcoal, graphite, or near-black setting; sculpted side and rim light; controlled contrast; realistic material texture and sheen; one restrained tonal prop at most; refined, quiet, high-end mood. Let a single product-derived color accent carry the composition. When text is requested, keep a large high-contrast headline in a deliberate text-safe area and a minimal icon or specification row. Never override a task-mandated pure-white background, no-text, or no-props instruction',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '暖调生活',
|
||||
prompt: '温暖生活风:暖米色背景,暖色灯光氛围,温馨的家居质感,亲和力强',
|
||||
prompt: 'Warm, cozy lifestyle e-commerce style for contextual and marketing images only: a believable cream, ivory, or warm-beige home setting; soft golden lamp light balanced with gentle window daylight; tactile bedding, knit, or natural wood texture; comforting and approachable atmosphere. Stage one simple usage story with the product as the unquestioned hero; use only category-relevant props and, if appropriate, a natural hand or person interacting with it. For family or children products, a softly styled nursery or bedroom is suitable. When text is requested, use a top headline zone, bold rounded sans-serif lettering, a short secondary pill or line, and small friendly feature icons. Never override a task-mandated pure-white background, no-text, or no-props instruction',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
label: '纯净棚拍',
|
||||
prompt: '标准电商棚拍:纯色浅背景,均匀的正面柔光,无杂物干扰,商品居中突出',
|
||||
prompt: 'Clean conversion-focused e-commerce studio style: seamless pure white or very light neutral background only where the task permits, even frontal softbox lighting with a subtle natural contact shadow, faithful color, crisp edge separation, and product filling the frame at a readable scale. No decorative props, lifestyle scenery, artificial reflections, or visual clutter. If the task explicitly requires copy or specifications, use a precise grid, a clear headline-first hierarchy, and sparse line icons; otherwise show no text. Never alter the product or contradict a task-specific background instruction',
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ async def list_assets(
|
||||
"width": a.width,
|
||||
"height": a.height,
|
||||
"error": a.error,
|
||||
"created_at": a.created_at,
|
||||
}
|
||||
for a in rows
|
||||
]
|
||||
|
||||
@@ -148,11 +148,13 @@ class SuiteCreateResponse(BaseModel):
|
||||
|
||||
class SuiteImageOut(BaseModel):
|
||||
item_id: str
|
||||
asset_id: str | None = None
|
||||
type_id: str
|
||||
name: str
|
||||
url: str
|
||||
status: str
|
||||
error: str | None = None
|
||||
completed_at: datetime | None = None
|
||||
|
||||
|
||||
class SuiteOut(BaseModel):
|
||||
|
||||
@@ -489,11 +489,12 @@ def suite_view(job: BackgroundJob, items: list[BackgroundJobItem]) -> dict[str,
|
||||
"created_at": job.created_at, "started_at": job.started_at, "completed_at": job.completed_at,
|
||||
"images": [
|
||||
{
|
||||
"asset_id": str(item.generated_asset_id) if item.generated_asset_id else None,
|
||||
"type_id": (item.payload or {}).get("kind", "custom"),
|
||||
"item_id": str(item.id),
|
||||
"name": (item.payload or {}).get("title", "AI 生图"),
|
||||
"url": item.result_url or "", "status": item.status,
|
||||
"error": item.error,
|
||||
"error": item.error, "completed_at": item.completed_at,
|
||||
}
|
||||
for item in items
|
||||
],
|
||||
|
||||
@@ -12,27 +12,27 @@ import re
|
||||
STYLE_SETS: dict[int, dict] = {
|
||||
1: {
|
||||
"name": "北欧极简",
|
||||
"tone": "Nordic minimalism: light gray or warm off-white background, soft diffused light, low-saturation palette, generous negative space, restrained and clean composition",
|
||||
"tone": "Nordic editorial e-commerce style for contextual and marketing images only: warm ivory or light greige base, soft window daylight with a subtle warm practical-light accent, low-saturation natural palette, pale wood and tactile linen details, spacious orderly composition, and at most one or two restrained lifestyle props. Keep the product exact and prominent. When text is requested, reserve a clean top text-safe zone with a clear headline-first hierarchy. Never override a task-mandated pure-white background, no-text, or no-props instruction",
|
||||
"bg": "",
|
||||
},
|
||||
2: {
|
||||
"name": "清新明亮",
|
||||
"tone": "Fresh and bright: white-to-pale-blue gradient background, high-key lighting, clear luminous colors, airy and uncluttered composition",
|
||||
"tone": "Fresh, bright, family-friendly e-commerce style for contextual and marketing images only: crisp white with pale sky-blue, mint, or other accents sampled from the product palette; high-key daylight, luminous but natural color, soft clean shadows, airy mobile-first composition, and a cheerful polished retail mood. When text is requested, use a generous top text-safe zone, one bold headline plus one short supporting line or pill, and small simple accent icons only when they clarify a real feature. Never override a task-mandated pure-white background, no-text, or no-props instruction",
|
||||
"bg": "",
|
||||
},
|
||||
3: {
|
||||
"name": "高级感深色",
|
||||
"tone": "Premium dark aesthetic: deep gray or charcoal background, dramatic side lighting, emphasize material texture and sheen, refined and restrained mood",
|
||||
"tone": "Premium dark editorial e-commerce style for contextual and marketing images only: deep charcoal, graphite, or near-black setting; sculpted side and rim light; controlled contrast; realistic material texture and sheen; one restrained tonal prop at most; refined, quiet, high-end mood. Let a single product-derived color accent carry the composition. When text is requested, keep a large high-contrast headline in a deliberate text-safe area and a minimal icon or specification row. Never override a task-mandated pure-white background, no-text, or no-props instruction",
|
||||
"bg": "",
|
||||
},
|
||||
4: {
|
||||
"name": "暖调生活",
|
||||
"tone": "Warm lifestyle: warm beige background, cozy warm lighting, inviting home-living texture, approachable and natural atmosphere",
|
||||
"tone": "Warm, cozy lifestyle e-commerce style for contextual and marketing images only: a believable cream, ivory, or warm-beige home setting; soft golden lamp light balanced with gentle window daylight; tactile bedding, knit, or natural wood texture; comforting and approachable atmosphere. Stage one simple usage story with the product as the unquestioned hero; use only category-relevant props and, if appropriate, a natural hand or person interacting with it. For family or children products, a softly styled nursery or bedroom is suitable. When text is requested, use a top headline zone, bold rounded sans-serif lettering, a short secondary pill or line, and small friendly feature icons. Never override a task-mandated pure-white background, no-text, or no-props instruction",
|
||||
"bg": "",
|
||||
},
|
||||
5: {
|
||||
"name": "纯净棚拍",
|
||||
"tone": "Clean e-commerce studio: plain light background, even frontal softbox lighting, no distracting props, product centered and prominent",
|
||||
"tone": "Clean conversion-focused e-commerce studio style: seamless pure white or very light neutral background only where the task permits, even frontal softbox lighting with a subtle natural contact shadow, faithful color, crisp edge separation, and product filling the frame at a readable scale. No decorative props, lifestyle scenery, artificial reflections, or visual clutter. If the task explicitly requires copy or specifications, use a precise grid, a clear headline-first hierarchy, and sparse line icons; otherwise show no text. Never alter the product or contradict a task-specific background instruction",
|
||||
"bg": "",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -164,6 +164,11 @@ interface GeneratedImageGridProps {
|
||||
export function GeneratedImageGrid({ images, selectedKeys, downloadingKeys, preview, onToggle, onDelete, onDownload, onEdit }: GeneratedImageGridProps) {
|
||||
const [page, setPage] = useState(1);
|
||||
const { current, visible } = useVisiblePage(images, page);
|
||||
// 新完成的图片经过时间倒序会进入首位;无论用户此前停在哪一页,都直接展示最新结果。
|
||||
const newestKey = images[0]?.key;
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
}, [newestKey]);
|
||||
useEffect(() => {
|
||||
if (current !== page) setPage(current);
|
||||
}, [current, page]);
|
||||
|
||||
@@ -215,15 +215,18 @@ export default function TrialSuitePanel({ product, assets, onRefreshAssets }: Pr
|
||||
const suiteImages = (suite?.images ?? [])
|
||||
.filter((img) => img.status === 'done')
|
||||
.map((img, index) => {
|
||||
const asset = generatedAssets.find((a) => assetUrl(a) === img.url);
|
||||
const asset = generatedAssets.find((a) => a.id === img.asset_id)
|
||||
|| generatedAssets.find((a) => assetUrl(a) === img.url);
|
||||
return {
|
||||
key: `suite-${suite?.id ?? 'current'}-${index}`,
|
||||
// 素材已回写时与历史记录共用同一个 key,避免同一张图显示两次。
|
||||
key: asset ? `asset-${asset.id}` : `suite-${suite?.id ?? 'current'}-${img.item_id || index}`,
|
||||
url: img.url,
|
||||
name: img.name,
|
||||
groupName: '套图结果',
|
||||
groupName: '生成结果',
|
||||
variantName: null as string | null,
|
||||
assetId: asset?.id,
|
||||
source: 'suite' as const,
|
||||
source: asset ? 'history' as const : 'suite' as const,
|
||||
createdAt: asset?.created_at || img.completed_at || suite?.completed_at || suite?.created_at || undefined,
|
||||
};
|
||||
});
|
||||
const historyImages = generatedAssets.map((asset) => ({
|
||||
@@ -236,10 +239,21 @@ export default function TrialSuitePanel({ product, assets, onRefreshAssets }: Pr
|
||||
source: 'history' as const,
|
||||
createdAt: asset.created_at,
|
||||
}));
|
||||
return [...suiteImages, ...historyImages].filter((img) => !hiddenResultKeys.has(img.key));
|
||||
const unique = new Map<string, ResultImage>();
|
||||
// 已落库的素材带有最终 created_at,后写入可覆盖同 key 的临时 suite 结果。
|
||||
[...suiteImages, ...historyImages].forEach((image) => unique.set(image.key, image));
|
||||
return [...unique.values()]
|
||||
.filter((img) => !hiddenResultKeys.has(img.key))
|
||||
.sort((left, right) => {
|
||||
const timestamp = (value?: string) => {
|
||||
const parsed = value ? Date.parse(value) : Number.NaN;
|
||||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
};
|
||||
const rightTime = timestamp(right.createdAt);
|
||||
const leftTime = timestamp(left.createdAt);
|
||||
return rightTime - leftTime || right.key.localeCompare(left.key);
|
||||
});
|
||||
}, [generatedAssets, hiddenResultKeys, suite]);
|
||||
const suiteResultImages = resultImages.filter((img) => img.source === 'suite');
|
||||
const historyResultImages = resultImages.filter((img) => img.source === 'history');
|
||||
const otherActiveSuiteTasks = activeSuiteTasks.filter((task) => task.id !== suite?.id);
|
||||
const selectedResultCount = resultImages.filter((img) => selectedResultKeys.has(img.key)).length;
|
||||
const selectedWatermarkItems = resultImages.filter((img) => selectedResultKeys.has(img.key) && img.assetId);
|
||||
@@ -1380,8 +1394,21 @@ export default function TrialSuitePanel({ product, assets, onRefreshAssets }: Pr
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
<FailedSuiteImages images={suite.images.filter((image) => image.status === 'failed')} />
|
||||
{suite.error && <Alert type="warning" showIcon message={suite.error} style={{ marginTop: 8 }} />}
|
||||
</>
|
||||
)}
|
||||
{resultImages.length > 0 && (
|
||||
<div style={suite ? { borderTop: '1px dashed #e5e7eb', paddingTop: 12, marginTop: 14 } : {}}>
|
||||
<Text strong>
|
||||
全部生成结果
|
||||
<Text type="secondary" style={{ fontWeight: 400, fontSize: 11, marginLeft: 8 }}>
|
||||
(按完成时间倒序 · 最新结果在第一页首位 · {resultImages.length} 张)
|
||||
</Text>
|
||||
</Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<GeneratedImageGrid
|
||||
images={suiteResultImages}
|
||||
images={resultImages}
|
||||
selectedKeys={selectedResultKeys}
|
||||
downloadingKeys={downloadingResultKeys}
|
||||
preview={imagePreviewConfig}
|
||||
@@ -1390,30 +1417,6 @@ export default function TrialSuitePanel({ product, assets, onRefreshAssets }: Pr
|
||||
onDownload={(image) => void handleDownloadResultImage(image)}
|
||||
onEdit={(image) => setGenModal({ url: assetById.get(image.assetId || '')?.original_url || image.url, name: image.name, afterAssetId: image.assetId })}
|
||||
/>
|
||||
<FailedSuiteImages images={suite.images.filter((image) => image.status === 'failed')} />
|
||||
{suite.error && <Alert type="warning" showIcon message={suite.error} style={{ marginTop: 8 }} />}
|
||||
</>
|
||||
)}
|
||||
{/* 单张 AI 生图结果(采集图片区「AI 生图」生成,回写 generated 组) */}
|
||||
{historyResultImages.length > 0 && (
|
||||
<div style={suite ? { borderTop: '1px dashed #e5e7eb', paddingTop: 12, marginTop: 14 } : {}}>
|
||||
<Text strong>
|
||||
生图记录
|
||||
<Text type="secondary" style={{ fontWeight: 400, fontSize: 11, marginLeft: 8 }}>
|
||||
(一键生成与单张生图的落库历史,刷新后仍保留 · {historyResultImages.length} 张)
|
||||
</Text>
|
||||
</Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<GeneratedImageGrid
|
||||
images={historyResultImages}
|
||||
selectedKeys={selectedResultKeys}
|
||||
downloadingKeys={downloadingResultKeys}
|
||||
preview={imagePreviewConfig}
|
||||
onToggle={toggleResultKey}
|
||||
onDelete={(image) => image.assetId && void handleDeleteAsset(image.assetId, [image.key])}
|
||||
onDownload={(image) => void handleDownloadResultImage(image)}
|
||||
onEdit={(image) => setGenModal({ url: assetById.get(image.assetId || '')?.original_url || image.url, name: image.name, afterAssetId: image.assetId })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function TrialVideoMaker({ productId, assets }: { productId: stri
|
||||
{jobs.filter((job) => job.status !== 'cancelled').length === 0 ? <Text type="secondary" style={{ display: 'block', marginTop: 10, fontSize: 12 }}>生成的轮播视频会保存在这里,可随时下载。</Text> : <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(230px, 1fr))', alignItems: 'start', gap: 10, marginTop: 12 }}>{jobs.filter((job) => job.status !== 'cancelled').slice(0, 8).map((job) => <VideoCard key={job.id} job={job} removing={removingId === job.id} downloading={downloadingId === job.id} onCancel={(id) => void cancel(id)} onDelete={(id) => void remove(id)} onRetry={(id) => void retry(id)} onDownload={(id) => void download(id)} />)}</div>}
|
||||
<Modal open={open} onCancel={() => setOpen(false)} onOk={() => void submit()} okText={`合成视频(${selected.length}/5)`} okButtonProps={{ disabled: selected.length < 2, loading: submitting }} width={900} title="选择轮播图片">
|
||||
<Alert type="info" showIcon message="AI 生成图排在前面;点击图片按播放顺序选择。" style={{ marginBottom: 12 }} />
|
||||
{candidates.length < 2 ? <Empty description="至少需要两张已转存图片" /> : <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, minmax(0, 1fr))', gap: 10, maxHeight: 430, overflowY: 'auto' }}>{candidates.map((asset) => { const index = selected.indexOf(asset.id); return <div key={asset.id} onClick={() => toggle(asset.id)} style={{ cursor: 'pointer', border: `2px solid ${index >= 0 ? '#1677ff' : 'transparent'}`, borderRadius: 7, padding: 2, position: 'relative' }}><Image preview={false} loading="lazy" src={urlOf(asset)} width="100%" style={{ aspectRatio: '3 / 4', objectFit: 'cover', borderRadius: 5 }} />{index >= 0 && <span style={{ position: 'absolute', top: 6, right: 6, background: '#1677ff', color: '#fff', borderRadius: 12, minWidth: 22, textAlign: 'center', lineHeight: '22px' }}>{index + 1}</span>}<div style={{ fontSize: 11, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{asset.group_key === 'generated' ? 'AI 图 · ' : ''}{nameOf(asset)}</div></div>; })}</div>}
|
||||
{candidates.length < 2 ? <Empty description="至少需要两张已转存图片" /> : <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, minmax(0, 1fr))', gap: 10, maxHeight: 510, overflowY: 'auto' }}>{candidates.map((asset) => { const index = selected.indexOf(asset.id); return <div key={asset.id} onClick={() => toggle(asset.id)} style={{ cursor: 'pointer', border: `2px solid ${index >= 0 ? '#1677ff' : 'transparent'}`, borderRadius: 7, padding: 2, position: 'relative' }}><Image preview={false} loading="lazy" src={urlOf(asset)} width="100%" style={{ aspectRatio: '3 / 4', objectFit: 'cover', borderRadius: 5 }} />{index >= 0 && <span style={{ position: 'absolute', top: 6, right: 6, background: '#1677ff', color: '#fff', borderRadius: 12, minWidth: 22, textAlign: 'center', lineHeight: '22px' }}>{index + 1}</span>}<div style={{ fontSize: 11, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{asset.group_key === 'generated' ? 'AI 图 · ' : ''}{nameOf(asset)}</div></div>; })}</div>}
|
||||
{selected.length > 0 && <Text type="secondary" style={{ display: 'block', marginTop: 10 }}>播放顺序即选中序号;取消后重新选择可调整顺序。</Text>}
|
||||
</Modal>
|
||||
</div>;
|
||||
|
||||
@@ -54,11 +54,11 @@ export interface ProductDimensions {
|
||||
|
||||
/** 视觉风格(默认提示词可在页面上改写,随生成请求覆盖后端模板) */
|
||||
export const STYLE_SET_OPTIONS = [
|
||||
{ value: 1, label: '北欧极简', prompt: 'Nordic minimalism: light gray or warm off-white background, soft diffused light, low-saturation palette, generous negative space, restrained and clean composition' },
|
||||
{ value: 2, label: '清新明亮', prompt: 'Fresh and bright: white-to-pale-blue gradient background, high-key lighting, clear luminous colors, airy and uncluttered composition' },
|
||||
{ value: 3, label: '高级感深色', prompt: 'Premium dark aesthetic: deep gray or charcoal background, dramatic side lighting, emphasize material texture and sheen, refined and restrained mood' },
|
||||
{ value: 4, label: '暖调生活', prompt: 'Warm lifestyle: warm beige background, cozy warm lighting, inviting home-living texture, approachable and natural atmosphere' },
|
||||
{ value: 5, label: '纯净棚拍', prompt: 'Clean e-commerce studio: plain light background, even frontal softbox lighting, no distracting props, product centered and prominent' },
|
||||
{ value: 1, label: '北欧极简', prompt: 'Nordic editorial e-commerce style for contextual and marketing images only: warm ivory or light greige base, soft window daylight with a subtle warm practical-light accent, low-saturation natural palette, pale wood and tactile linen details, spacious orderly composition, and at most one or two restrained lifestyle props. Keep the product exact and prominent. When text is requested, reserve a clean top text-safe zone with a clear headline-first hierarchy. Never override a task-mandated pure-white background, no-text, or no-props instruction' },
|
||||
{ value: 2, label: '清新明亮', prompt: 'Fresh, bright, family-friendly e-commerce style for contextual and marketing images only: crisp white with pale sky-blue, mint, or other accents sampled from the product palette; high-key daylight, luminous but natural color, soft clean shadows, airy mobile-first composition, and a cheerful polished retail mood. When text is requested, use a generous top text-safe zone, one bold headline plus one short supporting line or pill, and small simple accent icons only when they clarify a real feature. Never override a task-mandated pure-white background, no-text, or no-props instruction' },
|
||||
{ value: 3, label: '高级感深色', prompt: 'Premium dark editorial e-commerce style for contextual and marketing images only: deep charcoal, graphite, or near-black setting; sculpted side and rim light; controlled contrast; realistic material texture and sheen; one restrained tonal prop at most; refined, quiet, high-end mood. Let a single product-derived color accent carry the composition. When text is requested, keep a large high-contrast headline in a deliberate text-safe area and a minimal icon or specification row. Never override a task-mandated pure-white background, no-text, or no-props instruction' },
|
||||
{ value: 4, label: '暖调生活', prompt: 'Warm, cozy lifestyle e-commerce style for contextual and marketing images only: a believable cream, ivory, or warm-beige home setting; soft golden lamp light balanced with gentle window daylight; tactile bedding, knit, or natural wood texture; comforting and approachable atmosphere. Stage one simple usage story with the product as the unquestioned hero; use only category-relevant props and, if appropriate, a natural hand or person interacting with it. For family or children products, a softly styled nursery or bedroom is suitable. When text is requested, use a top headline zone, bold rounded sans-serif lettering, a short secondary pill or line, and small friendly feature icons. Never override a task-mandated pure-white background, no-text, or no-props instruction' },
|
||||
{ value: 5, label: '纯净棚拍', prompt: 'Clean conversion-focused e-commerce studio style: seamless pure white or very light neutral background only where the task permits, even frontal softbox lighting with a subtle natural contact shadow, faithful color, crisp edge separation, and product filling the frame at a readable scale. No decorative props, lifestyle scenery, artificial reflections, or visual clutter. If the task explicitly requires copy or specifications, use a precise grid, a clear headline-first hierarchy, and sparse line icons; otherwise show no text. Never alter the product or contradict a task-specific background instruction' },
|
||||
] as const;
|
||||
|
||||
/** 试算页固定目标平台 Ozon:俄文文案 · 3:4 图片 */
|
||||
@@ -130,11 +130,13 @@ export interface SuiteGeneratePayload {
|
||||
|
||||
export interface SuiteImageInfo {
|
||||
item_id: string;
|
||||
asset_id?: string | null;
|
||||
type_id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
status: string;
|
||||
error?: string | null;
|
||||
completed_at?: string | null;
|
||||
}
|
||||
|
||||
export interface SuiteInfo {
|
||||
|
||||
Reference in New Issue
Block a user